LeetCode 678. 有效的括号字符串(贪心,动规) / 4. 寻找两个正序数组的中位数(二分,关键题) / 447. 回旋镖的数量
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 678. 有效的括号字符串(贪心,动规) / 4. 寻找两个正序数组的中位数(二分,关键题) / 447. 回旋镖的数量相关的知识,希望对你有一定的参考价值。
678. 有效的括号字符串
2021.9.12 每日一题
题目描述
给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:
任何左括号 ( 必须有相应的右括号 )。
任何右括号 ) 必须有相应的左括号 ( 。
左括号 ( 必须在对应的右括号之前 )。
* 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
一个空字符串也被视为有效字符串。
示例 1:
输入: “()”
输出: True
示例 2:
输入: “(*)”
输出: True
示例 3:
输入: “(*))”
输出: True
注意:
字符串大小将在 [1,100] 范围内。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parenthesis-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
我刚开始是想用数字记录左右字符和特殊字符,然后判断的,然后从左往右写的时候,发现,从左到右判断右括号是否多余很好判断,而左括号不太容易判断;然后就想到了反过来不就能判断左括号了吗,然后就返回来写了,就过了
class Solution {
public boolean checkValidString(String s) {
//看到的第一反应当然还是栈了,但是后来感觉计数就行
//写代码的时候,发现正反向各遍历一次好像是可以的
int l = s.length();
int count = 0;
int left = 0;
int right = 0;
for(int i = 0; i < l; i++){
char c = s.charAt(i);
if(c == '('){
left++;
}else if(c == ')'){
left--;
}else{
count++;
}
//如果右括号多了,那么就需要用特殊符号补充
if(left < 0){
count--;
left++;
//如果没有特殊符号了,就不行了
if(count < 0){
//System.out.println("1 " + i);
return false;
}
}
}
//反过来
count = 0;
for(int i = l - 1; i >= 0; i--){
char c = s.charAt(i);
if(c == ')'){
right++;
}else if(c == '('){
right--;
}else{
count++;
}
//如果左括号多了,那么就需要用特殊符号补充
if(right < 0){
count--;
right++;
//如果没有特殊符号了,就不行了
if(count < 0){
//System.out.println("2 " + i);
return false;
}
}
}
return true;
}
}
看了官解的贪心,直呼秒啊
还是上面从左到右遍历的思路,但是现在遇到星号,因为可能是左括号,也可能是右括号,所以维护一个最大值和最小值,表示未匹配的左括号的最大值和最小值
如果遍历过程中遇到左括号,那么max和min都加1
如果遍历过程中遇到右括号,那么max和min都减1
如果遍历过程中遇到星号,那么max加1,就是将星号变成左括号,min减1,将星号变成右括号
如果min为0了,就不减了,意思是在这个过程中未匹配的左括号数量不能少于0
如果max小于0了,那么说明右括号多了,所以返回false
遍历到最后,如果min大于0,说明左括号多了,false,如果等于0,说明都可以被匹配,true
挺难理解的,不如前后双向遍历
class Solution {
public boolean checkValidString(String s) {
//官解贪心
int l = s.length();
int count = 0;
int left = 0;
int max = 0;
int min = 0;
for(int i = 0; i < l; i++){
char c = s.charAt(i);
if(c == '('){
min++;
max++;
}else if(c == ')'){
min--;
max--;
}else{
max++;
min--;
}
//如果右括号多了,那么就需要用特殊符号补充
if(max < 0){
return false;
}
if(min < 0)
min = 0;
}
return min == 0;
}
}
官解给的动态规划也非常值得研究,又是一个不普通的动态规划
class Solution {
public boolean checkValidString(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
//一个字符的情况
for (int i = 0; i < n; i++) {
if (s.charAt(i) == '*') {
dp[i][i] = true;
}
}
//两个字符的情况
for (int i = 1; i < n; i++) {
char c1 = s.charAt(i - 1), c2 = s.charAt(i);
dp[i - 1][i] = (c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*');
}
//动规
for (int i = n - 3; i >= 0; i--) {
char c1 = s.charAt(i);
for (int j = i + 2; j < n; j++) {
char c2 = s.charAt(j);
//第一种情况,看左右两个边界字符
if ((c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')) {
dp[i][j] = dp[i + 1][j - 1];
}
//第二种情况,分成两段,如果两段都是合法的,总体也是合法的,这里很关键
for (int k = i; k < j && !dp[i][j]; k++) {
dp[i][j] = dp[i][k] && dp[k + 1][j];
}
}
}
return dp[0][n - 1];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/valid-parenthesis-string/solution/you-xiao-de-gua-hao-zi-fu-chuan-by-leetc-osi3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4. 寻找两个正序数组的中位数
题目描述
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-10^6 <= nums1[i], nums2[i] <= 10^6
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
拖了很久没做,昨天同学笔试遇到了,今天赶紧来补一下
第一种方法,二分,主要就是利用有序,然后每次取中位数的一半,进行排除
这个方法的时间复杂度是log(m + n)
class Solution {
int l1;
int l2;
int[] nums1;
int[] nums2;
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//第一个方法,二分查找
//因为要找到的是中位数,假设是第k个数
//然后首先在两个数组中分别找第 k / 2 个数,然后比较两个数的大小,比如A[i]<B[j],
//因为比A[i]小的数最多有(k/2-1) * 2个,所以A[i]不可能是中位数
//所以排除A[i]及A数组前面的数
//然后现在找的是第k/2个数
//然后以此类推,要注意是否越界
this.nums1 = nums1;
this.nums2 = nums2;
l1 = nums1.length;
l2 = nums2.length;
int sum = l1 + l2;
if(sum % 2 == 1){
int mid = sum / 2;
double res = getK(mid + 1, 0, 0);
return res;
}else{
int mid1 = sum / 2 - 1;
int mid2 = sum / 2;
double res = (getK(mid1 + 1, 0, 0) + getK(mid2 + 1, 0, 0)) / 2.0;
return res;
}
}
//找第k个数,第一个数组开始下标是left1,第二个是left2
public int getK(int k, int left1, int left2){
if(left1 == l1)
return nums2[left2 + k - 1];
if(left2 == l2)
return nums1[left1 + k - 1];
if(k == 1)
return nums1[left1] < nums2[left2] ? nums1[left1] : nums2[left2];
int t = k / 2;
int newleft1 = Math.min(l1 - 1, left1 + t - 1);
int newleft2 = Math.min(l2 - 1, left2 + t - 1);
int v1 = nums1[newleft1];
int v2 = nums2[newleft2];
//如果v1<v2,那么排除nums1中的数
if(v1 <= v2){
int cut = newleft1 - left1 + 1; //排除了这么多数
left1 = newleft1 + 1;
return getK(k - cut, left1, left2);
}else{
int cut = newleft2 - left2 + 1; //排除了这么多数
left2 = newleft2 + 1;
return getK(k - cut, left1, left2);
}
}
}
第二个方法,根据中位数的性质,小于等于它的数(左边)和大于等于它的数(右边)个数相同,且左边小于等于右边
所以对于两个数组,可以先对两个数组进行划分,然后判断划分点的关系
如果满足:
第 1 个数组分割线左边的第 1 个数小于等于第 2 个数组分割线右边的第 1 的数
第 2 个数组分割线左边的第 1 个数小于等于第 1 个数组分割线右边的第 1 的数
就说明,左边是小于右边的,那么如果左边的个数加起来是一半的话,那么中位数就可以很方便的取到(分奇偶情况)
注释非常详尽了
这样,只需要在较短的数组里二分,复杂度是logm
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//利用中位数的性质来做一下
int l1 = nums1.length;
int l2 = nums2.length;
//保证l1 < l2
if(l1 > l2){
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
int len = l1;
l1 = l2;
l2 = len;
}
//左边要有half个数字
int half = (l1 + l2 + 1) / 2;
//在第一个数组里,找分割线,可以是0或者m,找到了第一个数组的分割线,第二个数组的分割线自然也就确定了
int left = 0;
int right = l1;
while(left < right){
//第一个数组中分割线的位置,分割线在第i个位置,前面就有i个数
int i = (right - left + 1) / 2 + left;
//第二个数组中分割线的位置,第二个数组中应该有half - i个数,所以分割线在half - i
int j = half - i;
//然后判断这个分割线是否满足条件
//如果i-1位置大于j位置,说明数组1的分割线靠右了,所以下次查找分割线向左移
if(nums1[i - 1] > nums2[j]){
right = i - 1;;
}else{
//否则,满足条件的
left = i;
}
}
//此时,找到了分割线
int i = left;
int j = half - i;
//特殊情况,如果i是0或者l1,j是0或者l2
//如果边界在最左边,那么将左边最大值赋值为MIN_VALUE
int leftmax1 = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
//如果边界在最右边,那么将右边最小值赋值为MAX_VALUE
int rightmin1 = i == l1 ? Integer.MAX_VALUE : nums1[i];
int leftmax2 = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
int rightmin2 = j == l2 ? Integer.MAX_VALUE : nums2[j];
int sum = l1 + l2;
//如果是奇数,那么就是左边最大值
if(sum % 2 == 1){
return Math.max(leftmax1, leftmax2);
}else{
//如果是偶数,那么就是左边最大值和右边最小值之和 / 2
return (Math.max(leftmax1, leftmax2) + Math.min(rightmin1, rightmin2)) / 2.0;
}
}
}
推广到两个有序数组中找第k个数,也是这种思路
这时候,因为数组长度不一定大于k,所以需要根据长度和k的较小值来进行二分
447. 回旋镖的数量
题目描述
给定平面上 n 对 互不相同 的点 points ,其中 points[i] = [xi, yi] 。回旋镖 是由点 (i, j, k) 表示的元组 ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。
返回平面上所有回旋镖的数量。
示例 1:
输入:points = [[0,0],[1,0],[2,0]]
输出:2
解释:两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]
示例 2:
输入:points = [[1,1],[2,2],[3,3]]
输出:2
示例 3:
输入:points = [[1,1]]
输出:0
提示:
n == points.length
1 <= n <= 500
points[i].length == 2
-10^4 <= xi, yi <= 10^4
所有点都 互不相同
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-boomerangs
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
注意距离为勾股定理那个距离,题目没写,写汉明距离错了
class Solution {
public int numberOfBoomerangs(int[][] points) {
int l = points.length;
//到i点,距离为d的点的数目
Map<Integer, Map<Integer, Integer>> map = new HashMap<>();
for(int i = 0; i < l; i++){
Map<Integer, Integer> temp = map.getOrDefault(i, new HashMap<>());
for(int j = 0; j < l; j++){
if(i == j)
continue;
int[] a = points[i];
int[] b = points[j];
int x = Math.abs(a[0] - b[0]);
int y = Math.abs(a[1] - b[1]);
int d = x * x + y * y;
temp.put(d, temp.getOrDefault(d, 0) + 1);
}
map.put(i, temp);
}
int res = 0;
for(int k : map.keySet()){
Map<Integer, Integer> temp = map.get(k);
for(int p : temp.keySet()){
int v = temp.get(p);
res += v * (v - 1);
}
}
return res;
}
}
一遍处理
class Solution {
public 以上是关于LeetCode 678. 有效的括号字符串(贪心,动规) / 4. 寻找两个正序数组的中位数(二分,关键题) / 447. 回旋镖的数量的主要内容,如果未能解决你的问题,请参考以下文章
[M贪心] lc678. 有效的括号字符串(括号问题+思维+模拟+dp)
LeetCode 678 有效的括号字符串[栈] HERODING的LeetCode之路
Leetcode刷题100天—678. 有效的括号字符串( 字符串)—day36