leetcode麻烦又易忘记题目

一,双指针

同种思路的相向双指针方法

有序数组的平方 做到 O(n)
找到 K 个最接近的元素
数组中的 K 个最强值 用双指针解决
两数之和 II - 输入有序数组
平方数之和
统计和小于目标的下标对数目
采购方案 同 2824 题
三数之和https://leetcode.cn/problems/3sum/description/
最接近的三数之和
四数之和https://leetcode.cn/problems/4sum/description/
有效三角形的个数
数的平方等于两数乘积的方法数 用双指针实现
三数之和的多种可能 1711

1, 四数之和
排序 + 双指针方法。 实现On3时间复杂度
思路:
穷举第一个数
再穷举第二个数
剩下两个数就转为了使用相向双指针寻找恰好让四个数和为target的方法

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<>();
        int n = nums.length;

        for(int i = 0; i < n - 3; ++i){
            long outi = nums[i];
            if(i > 0 && outi == nums[i - 1]) continue;
            

            for(int j = i + 1; j < n - 2; ++j){
                long outj = nums[j];
                //这里用例中虽然溢出了,不担心int强转溢出问题是因为这个用例刚好没有符合条件的组合
                if(j > i + 1 &&outj== nums[j - 1]) continue;
                
                int left = j + 1;
                int right =  n - 1;
                while(left < right){
                    long sum = outi + outj + nums[left] + nums[right];
                    if(sum > target){
                        -- right;
                    }else if(sum < target){
                        ++ left;
                    }else{
                        ans.add(List.of((int)outi,(int)outj,nums[left],nums[right]));
                        for(left++; left < right && nums[left] == nums[left - 1]; ++ left);
                        for(right --; left < right && nums[right] == nums[right + 1]; -- right);
                    }
                }

            }
        }
        return ans;
    }
}

木桶原理

1,盛最多水的容器
https://leetcode.cn/problems/container-with-most-water/description/

class Solution {
    public int maxArea(int[] height) {
        //这个题就是移动短边
        //从题意上,假设以第一个数为左侧边,然后应该穷举右侧边直到最后一个数
        //然后再假设以第二个数为左侧边,穷举右侧边直到最后一个数。
        //依次类推,暴力。On2
        //但是双指针可以实现On的复杂度,同时借助left和right只移动短边
        //就代表它所匹配的另一侧的长边,当前的面积是最大值。
        //例如:1,8,6,2,5,4,8,3,7
        //第一次left的1最短,因为right-left已经是最大,1不管匹配对侧的长边
        //包括8,6,2,5,4,8,3这些都不用再遍历了,因为1和7对应的right-left最大
        //此时面积就是这些中最大的
        int left = 0;
        int right = height.length - 1;
        int ans = 0;
        while(left < right){
            int area = (right - left) * Math.min(height[left], height[right]);
            ans = Math.max(ans, area);
            if(height[left] < height[right]){
                ++ left;
            }else{
                -- right;
            }
        }
        return ans;
    }
}

2,接雨水
https://leetcode.cn/problems/trapping-rain-water/

class Solution {
    public int trap(int[] height) {
        //应该把数组每一个位置当作一个桶,这个桶由左侧和右侧组成
        //而当前的数值大小,可以认为是在这个桶上填满了heigth[i]的石头
        //这个桶剩余的大小才是雨水的。
        //而这个桶的大小取决于左侧边,右侧边中较小的那个,短板
        //而left和right两个桶,根据lmax和rmax的大小比较,优先计算已经确定较小边的那个
        //例如:对于8,0,10,6这个序列。
        //left对应的8已经确定左侧边最小是8,但是它的右侧边此时只知道最小是6,在8到6中间数组数据中有没有比6更大的,不确定
        //而right对应的6右侧最小边是确定的了,但是左侧边不确定,现在只知道至少有个8了。但是有这个8就够了,因为即使8和6之间有一个10,但是桶取决于最小边6. 那如果有一个比6 更小的呢,例如3,那这个不影响当前的right这个桶啊,因为这个桶右侧必然是6了,同时左侧已经确定至少有一个8了,3即使比6小,但他不可能作为当前这个right这个桶两个边。
        int left = 0;
        int right = height.length - 1;
        int lmax = 0;
        int rmax = 0;
        int ans = 0;
        while(left <= right){
            lmax = Math.max(lmax, height[left]);
            rmax = Math.max(rmax, height[right]);
            if(lmax <= rmax){
                ans += lmax - height[left];
                ++ left;
            }else{
                ans += rmax - height[right];
                -- right;
            }
        }
        return ans;

    }
}

同向双指针

1,删除最短的子数组使剩余数组有序
https://leetcode.cn/problems/shortest-subarray-to-be-removed-to-make-array-sorted/description/

class Solution {
    public int findLengthOfShortestSubarray(int[] arr) {
        //同向双指针
        //思路:既然要找非递减序列,虽然含有等值部分,下面以递增相称。
        //第一:找一个最长的递增子序列起始点,所以从右侧遍历
        int n = arr.length;
        int right = n - 1;
        while(right >= 1 && arr[right - 1] <= arr[right]){
            -- right;
        }
        //经过while寻找后,是右侧最长递增子序列起始点。
        if(right == 0) return 0;  //right 都等于0了,说明整个数组就是递增的,不需要删除子数组
        //第二:如果不是全部递增的,左侧部分 + 右侧部分组合可能也是递增的,这时
        //删除的子数组可能更短。
        int ans = right;  //默认删除左侧的全部数的子数组
        int left = 0;
        while(left == 0 || arr[left - 1] <= arr[left]){
            while(right < n && arr[left] > arr[right]){
                ++ right;
            }
            ans = Math.min(ans, right-left-1);  // 注意这个right-left-1是只删除left和right之间的数,例如:2,5,3,只是删除5这个数的长度
            ++ left;
        }
        //第三:为什么上面的while可以考虑到所有的删除子数组情况呢??
        //因为题目要求只删除一个,注意是一个连续子数组,所以最简单的思考方式是
        //让left 和 right两侧尽可能长, 中间部分就是那个删除的最短的子数组
        //那为什么不让 中间部分 + right串最长呢? 不可能,会超过一个
        //那为什么不让 left串 + 中间部分串最长呢? 不可能,会超过一个
        return ans;
    }
}

2,统计移除递增子数组的数目 II
https://leetcode.cn/problems/count-the-number-of-incremovable-subarrays-ii/description/

class Solution {
    //既然是子数组,就涉及到一个枚举的问题?
    //肯定不能暴力枚举,是n2做法,肯定会超时
    //所以子数组的方法一般就枚举一个端点, 另一端数学统计有多少个,就减少了时间复杂度
    public long incremovableSubarrayCount(int[] nums) {
        //情况一:整个数组完整递增,此时i=n-1
        int n = nums.length;
        int left = 0;
        while(left < n - 1 && nums[left] < nums[left + 1]){
            ++ left;
        }
        if(left == nums.length - 1){
            return (long)n * (n + 1) / 2;
        }
        //情况二:不是整个数组递增的情况。
        //从题意看只删除一个数组,所以其实不用考虑波动的情况
        //例如:1,2,3,1,4,1. 要考虑把4也作为一个递增数组一部分的话,这时必须删除两个子数组了,与题意不符,所以这种波动情况不用考虑
        //那么就是把数组分为前缀部分 + 后缀部分即可,连续删除一个子数组
        long ans = 0;
        ans += left + 2;  //这个实际上代表提前枚举了,n-1这个作为删除子串的右端点
                            //也就是后缀串的长度为0

        int right = n - 1;
        //while条件不能用right > 0 && nums[right - 1] < nums[right]
        //这种条件,缺少了以后缀最小的数那次循环。例如:1,2,4,3,2,5,7
        //就是缺少了7,5,2这个2是后缀为起点时的一种情况
        //下面这种条件,right实际代表的是后缀串的 第一个元素
        while(right == n-1 || nums[right] < nums[right + 1]){
            while(left >= 0 && nums[left] >= nums[right]){
                -- left;
            }
            ans += left + 2;
            -- right;
        }
        return ans;
    }
}

3,同向双指针实现指定数值整体后移的操作模板
leetcode:移动零
https://leetcode.cn/problems/move-zeroes/

class Solution {
    //同向条件滑动指针,实现指定数值0 全写到数组后面
    public void moveZeroes(int[] nums) {
        int k = 0;
        for(int tmp : nums){
            if(tmp != 0){
                nums[k] = tmp;
                ++ k;
            }
        }
        for(int i = k; i < nums.length; ++i){
            nums[i] = 0;
        }
    }
}

4,同向双指针实现数组整体后移的模板
leetcode:复写零
https://leetcode.cn/problems/duplicate-zeros/

class Solution {
    //同向错位指针,实现模拟数组的整体移动功能建模。
    public void duplicateZeros(int[] arr) {
        int zeroCount = 0;
        for(int tmp : arr){
            if(tmp == 0) ++ zeroCount;
        }

        int pre = arr.length - 1;
        int last = arr.length + zeroCount - 1;

        while(pre >= 0){
            if(last < arr.length){
                arr[last] = arr[pre];
            }
            //如果arr[pre]是0,因为要复写0,所以last要多移动一个用0填充。
            //如果题目没有这个要求,只是
            if(arr[pre] == 0){
                -- last;
                if(last < arr.length){
                    arr[last] = 0;
                }
            }
            -- pre;
            -- last;
        }

    }

}

双序列双指针

加法模板

while(A没遍历完  || B没遍历完)
 	    拿到两个数的个位加数
 	    和 = A 的当前位 + B 的当前位 + 进位carry
 	    相加位 =   carry % 10
 	    进位 = carry / 10
加完后,要判断进位是否不是0

1,数组形式的整数加法
https://leetcode.cn/problems/add-to-array-form-of-integer/description/

class Solution {
    public List<Integer> addToArrayForm(int[] num, int k) {
        int n = num.length;
        List<Integer> res = new ArrayList<>();
        int sum = 0;
        int i = n - 1;
        int carry = 0;  //表示进位

        //A没遍历完 或者 B没遍历完
        while(i >= 0 || k != 0){
            //拿到两个个位加数
            int leftGewei = i >= 0 ? num[i] : 0;
            int rightGewei = k != 0 ? k % 10 : 0;

            sum = leftGewei + rightGewei + carry;
            //更新两个加数  和 进位
            carry = sum / 10;
            k = k / 10;
            -- i;
            res.add(0, sum % 10);
        }
        //加完后,要判断进位是否不是0
        if(carry != 0) res.add(0, carry);
        return res;
    }
}

比较模板

while(A 没有遍历完 && B没有遍历完)
	charA = A.charAt(i)
	charB = B.charAt(j)
	while得到charA的字符长度
	while得到charB的字符长度
	判断不符合条件的结果
判断是否都已达到字符串最长的长度

2,情感丰富的文字
https://leetcode.cn/problems/expressive-words/description/

class Solution {
    public int expressiveWords(String S, String[] words) {
        int count = 0;
        for(String tmp : words){
            if(isExpand(S, tmp)) ++ count;
        }
        return count;
    }

    // 
    private boolean isExpand(String s, String t) {
        int n = s.length();
        int m = t.length();
        if(n < m) return false;
        int i = 0;
        int j = 0;
        while(i < n && j < m){
            char leftChar = s.charAt(i);
            char rightChar = t.charAt(j);
            int cntA = 0;
            while(i < n && s.charAt(i) == leftChar){
                ++ cntA;
                ++ i;
            }

            int cntB = 0;
            while(j < m && t.charAt(j) == rightChar){
                ++ cntB;
                ++ j;
            }

            if(leftChar != rightChar || cntA < cntB ||
              cntA <= 2 && cntA != cntB) return false;
        }
        //这个主要就是判断,是否多出了字符
        return i == n && j == m;
    }


}

3, 合并两个二维数组 - 求和法
https://leetcode.cn/problems/merge-two-2d-arrays-by-summing-values/description/

class Solution {
    public int[][] mergeArrays(int[][] nums1, int[][] nums2) {
        //双序列,无非就是对当前两个指针指向的元素进行判断
        //a大时,a小时, a=b时,分别移动哪个指针
        int i = 0;
        int n = nums1.length;
        int j = 0;
        int m = nums2.length;
        List<List<Integer>> ans = new ArrayList<>();
        while(true){
            if(i == n){
                while(j < m){
                    ans.add(List.of(nums2[j][0], nums2[j][1]));
                    ++ j; 
                }
                break;
            }
            if(j == m){
                while(i < n){
                    ans.add(List.of(nums1[i][0],nums1[i][1]));
                    ++ i;
                }
                break;
            }

            int key1 = nums1[i][0];
            int key2 = nums2[j][0];

            if(key1 < key2){
                ans.add(List.of(key1, nums1[i][1]));
                ++ i;
            }else if(key1 > key2){
                ans.add(List.of(key2, nums2[j][1]));
                ++ j;
            }else{
                int sum = nums1[i][1] + nums2[j][1];
                ans.add(List.of(key1, sum));
                ++ i;
                ++ j;
            }
        }

        //stream()是表示启动流
        //map一般是计算并收集返回的结果
        //toArray()是结束流,并将收集的结果以什么类型返回
        return ans.stream()
            .map(inner-> inner.stream().mapToInt(z -> z).toArray())
            .toArray(int[][]::new);
    }
}

4,一大一小指针,实现可重复组合统计
早餐组合
https://leetcode.cn/problems/2vYnGI/

class Solution {
    public int breakfastNumber(int[] staple, int[] drinks, int x) {
        Arrays.sort(staple);
        Arrays.sort(drinks);
      
        int first = 0;
        int last = drinks.length - 1;
        int ans = 0;
        int mod = 1000000007;
        while(first < staple.length && last >= 0){
            //最小 + 最大都大于x了。 
            //所以这些跳过去的drink,加后面更大的staple,肯定也是大于x的
            //所以可以不必遍历
            if(staple[first] + drinks[last] > x){
                -- last;
            }else{
                ans = (ans + last + 1) % mod;
                ++ first;
            }
        }
        return ans;

    }
}

5,区间,这道题挺绕的

经过等价转换: arr1[i] - d <= arr2[j] <= arr1[i] + d,在这个区间的都是不符合距离值定义的。
那么很容易想到把数组1,和2都从小到大排序,遍历arr1数组,只要arr2数组的最小值 大于 arr1[i] +d ,或者 arr2数组的最大值 小于 arr1[i] - d ,就不在这个范围内了,就可以ans ++,计算符合距离值定义的数量了。但是这个思路是错误的,缺少了一种情况: 这个【arr1[i] - d, arr1[i] + d】可能正好在arr2 的中间没有空缺的部分,例如:arr2排序后是【1,7,8】,而【arr1[i] - d, arr1[i] + d】是【2,4】,这时候,arr2也是符合距离值定义的,但是上面的思路没有统计这种情况。
修改的代码如下:
其中arr2[j] > x + d 部分就包含了两种情况。
j == arr2.length,是第三种情况。共三种都包含了。

class Solution {
    public int findTheDistanceValue(int[] arr1, int[] arr2, int d) {
        Arrays.sort(arr1);
        Arrays.sort(arr2);
        int ans = 0;
        int j = 0;
        for (int x : arr1) {
            while (j < arr2.length && arr2[j] < x - d) {
                j++;
            }
            if (j == arr2.length || arr2[j] > x + d) {
                ans++;
            }
        }
        return ans;
    }
}

6, 长按键入—也是采用上面的“比较模板”

while循环,相同同时加指针。 不同,只加一个。
https://leetcode.cn/problems/long-pressed-name/

class Solution {
    public boolean isLongPressedName(String name, String typed) {
        int p1 = 0;
        int p2 = 0;

        int n = name.length();
        int m = typed.length();
        //判断前缀字符不同  和  内部存在字符不同的情况
        while(p1 < n && p2 < m){
            char leftChar = name.charAt(p1);
            char rightChar = typed.charAt(p2);

            int cntLeft = 0;
            while(p1 < n && name.charAt(p1) == leftChar){
                ++ p1;
                ++ cntLeft;
            }
            int cntRight = 0;
            while(p2 < m && typed.charAt(p2) == rightChar){
                ++ p2;
                ++ cntRight;
            }

            if(leftChar != rightChar || cntLeft > cntRight) return false;
        }

        //存在多余的不同的字符的情况
        return p1 == n && p2 == m;
    }
}

其他

1,分割两个字符串得到回文串

class Solution {
    public boolean checkPalindromeFormation(String a, String b) {
        //左右寻找到不匹配的部分
        //中间剩余的left 到 right部分的串如果是回文的了
        //那么 apre1+bsuf1 或者 apre2+bsuf2有一个就是能够拼接成回文的串。
        //必须 a 和 b的check都有,一个acheck表示的以 a为前缀, b为后缀的两种情况
        //第二个bcheck,表示以b为前缀,a为后缀的两种情况
        return check(a, b) ||  check(b, a);
    }
    //a和b必须都检查是不是中间剩余串可以符合回文串
    //如果只判断a的中间剩余串不是回文串,就认为不能拼接成回文串,
    //这是漏掉了b的中间串是回文串, a的前缀 + b含这个b的中间串的后缀串可以拼接成回文串的情况
    public boolean check(String a, String b){
        int left = 0;
        int right = b.length() - 1;
        while(left < right && a.charAt(left) == b.charAt(right)){
            ++ left;
            -- right;
        }
        return isPalindrome(a, left, right) || isPalindrome(b, left, right);
    }

    public boolean isPalindrome(String s, int i, int j){
        while(i < j){
            if(s.charAt(i) == s.charAt(j)){
                ++ i;
                -- j;
            }else{
                return false;
            }
        }
        return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值