zoukankan      html  css  js  c++  java
  • 数据结构,滑动窗口,双指针,LeetCode,相关题目汇总

    209题:

    209题目Leetcode连接

    题解:双指针,或者我们更喜欢叫它滑动窗口,不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。这里的窗口就是:和 ≥ target 的长度的连续 子数组,如果当前窗口的值小于target ,不断移动右边界,就是更加窗口的值,如果当前窗口的值大于target 了,窗口就要向前移动了,说白了就是要减少了才行,注意这里是符合条件的,所以要将长度记录下来,另外这里要用while(精髓在这里),不断更新这个窗口直到把所有 ≥ target的连续子数组找出来,然后取他们的最小值。最后直到把这个数组遍历完就可以获得最小值了。

    class Solution {
       public int minSubArrayLen(int target, int[] nums) {
           if (nums == null || nums.length < 1){
               return 0;
          }
           int left = 0;
           int sum = 0;
           int ret = Integer.MAX_VALUE;
           for (int right = 0; right < nums.length; right++) {
               sum += nums[right];
               while (sum >= target){
                   ret = Math.min(ret , right-left + 1);
                   sum -= nums[left];
                   left++;
              }
          }
           return ret == Integer.MAX_VALUE?0:ret;
      }
    }

    74题:

    74题目Leetcode连接

    题解:由于二维矩阵固定列的「从上到下」或者固定行的「从左到右」都是升序的,可以从第一行最后一列开始(设为cur变量),分为几种情况:

    cur > target时:说明就在这一行,只要减列就行了

    cur == target时:找到了,可以直接返回

    cur < target时:说明这一行都不会有这个数,去下一行找,也从下一行的最后一个数开始

    依次下去,如果遍历完了还没有找到直接返回false

    class Solution {
       public boolean searchMatrix(int[][] matrix, int target) {
           if(matrix == null || matrix.length < 1)
               return false;
           int i = 0;
           int j = matrix[0].length-1;
           while (i<matrix.length && j >= 0){
               int cur = matrix[i][j];
               if (cur == target){
                   return true;
              }else if(cur > target){
                   j--;
              }else {
                   i++;
              }
          }
           return false;
      }
    }

    260题:

    260题目Leetcode连接

    方法一:滑动窗口,先排序,然后用一个长度为2的窗口去遍历整个数组,相同的直接跳过,不同的就根据情况判断获取这两个值,分为以下几种情况:

    • 下标i跟i-1的值相同,往后跳动两个格子,如果跳动一个格子,就已经到顶了,说明最后一个就是我们要找的最后一个数。例如:[0,1,1,2]

    • 下标i跟i-1的值相同,往后跳动两个格子,如果跳动一个格子,没有到顶部,那就continue;

    • 下标i跟i-1的值不同,首先说明的是i-1肯定是我们想要的值了,先存起来。然后我们只要往后跳动一个格子,这里需要判断一下i是不是到了顶部,如果是的话,最后一个值也是我们需要的,不是的话就继续滑动。

    class Solution {
       public int[] singleNumber(int[] nums) {
           int[] res = new int[2];
           if (nums == null || nums.length < 2){
               return res;
          }
           Arrays.sort(nums);
           int index = 0;
           for (int i = 1; i < nums.length; i++) {
               if (nums[i] == nums[i-1]){
                   i++;
                   if (i == nums.length-1){
                       res[1] = nums[i];
                       return res;
                  }
                   continue;
              }
               res[index++] = nums[i-1];
               if (i == nums.length-1){
                   res[index++] = nums[i];
              }
               if (index == 2){
                   return res;
              }
          }
           return null;
      }
    }

    方法二:用集合,我们可以使用一个哈希映射统计数组中每一个元素出现的次数。在统计完成后,我们对哈希映射进行遍历,将所有只出现了一次的数放入答案中。不推荐

    class Solution {
      public int[] singleNumber(int[] nums) {
          int[] res = new int[2];
          Map<Integer , Integer> map = new HashMap<>();
          for(int num : nums){
              map.put(num,map.getOrDefault(num , 0)+1);
          }
          int index = 0;
          for (Integer key : map.keySet()){
              if (map.get(key) == 1){
                  res[index++] = key;
              }
          }
          return res;
      }
    }

    方法三:位运算,异或运算有性质如下:

    • 任何数和 0 做异或运算,结果仍然是原来的数,即 a ⊕ 0 = a。

    • 任何数和其自身做异或运算,结果是 0,即 a ⊕ a = 0

    • 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。

    题解:基于位运算性质

    1. 先将数组的所有元素异或得到的一个结果,这个结果为不存在重复的两个元素异或的结果,因为相同的都已经抵消掉了,同时也不为0,因为这两个元素是不同的。

    2. 然后我们需要将来数组分为两组,一组包含其中一个我们需要的结果,另外一组包含另外一个我们需要的结果,同时相同的元素必须分到一组,这样,我们对每一组的所有元素分别进行异或,就可以在每一组中得到一个我们想要的结果,怎么做呢?

    3. lab &= ‑lab得到出 lab最右侧的1,因为异或值为1,说明我们需要的两个值里面其中一个为0,另外一个为1,这样才能异或为1

    4. 然后遍历,分组,每一组分别异或就可以了

    class Solution {
       public int[] singleNumber(int[] nums) {
           int[] res = new int[2];
           int lab = 0;
           for(int num : nums){
               lab ^= num;
          }
           lab &= -lab;
           for(int num : nums){
               if ((num & lab) != 0){
                   res[0] ^= num;
              }else {
                   res[1] ^= num;
              }
          }
           return res;
      }
    }

    219题:

    219题目Leetcode连接

    题解:采用滑动窗口,窗口长度最长为k,若长度不超过k只移动right边界即可,每次要遍历窗口内的每一个元素来判断是否和right相等。如果相等就直接返回,若长度超过k,则需要同时移动left。

    class Solution {
       public boolean containsNearbyDuplicate(int[] nums, int k) {
           int left = 0;
           for (int right = 0; right < nums.length; right++) {
               if (right - left > k ){
                   left++;
              }
               for (int i = left; i < right; i++) {
                   if (nums[i] == nums[right]){
                       return true;
                  }
              }
          }
           return false;
      }
    }

    击败率不高,想了一下,应该是每次要遍历增加了时间复杂度,后面改成了HashSet来维持窗口。击败了高了一点。

    class Solution {
       public boolean containsNearbyDuplicate(int[] nums, int k) {
           Set<Integer> set = new HashSet<>();
           for (int i = 0; i < nums.length; i++) {
               if (set.contains(nums[i])){
                   return true;
              }
               set.add(nums[i]);
               if (set.size() > k){
                   set.remove(nums[i-k]);
              }
          }
           return false;
      }
    }

    643题:

    643题目Leetcode连接

    题解:滑动窗口,用一个大小为k的窗口从数组的开始,每次往后移动一格,取每次滑动的窗口的和的最大值,最后用这个最大值/k,就是最终我们需要的结果了,这里需要注意一下的是:我刚开始计算和的时候每次都是从窗口的最左边算到最右边,这样会有很多的重复计算,也会超出时间限制,我们可以利用上一次窗口的和,只需要用这个和减掉nums[left-1],然后加上最新的nums[right]就行了

    class Solution {
       public double findMaxAverage(int[] nums, int k) {
           if (nums == null || nums.length < k){
               return 0;
          }
           int left = 0;
           int sum = 0;
           for (int i = 0; i < k; i++) {
               sum += nums[i];
          }
           int res = sum;
           for (int right = k; right < nums.length; right++) {
               if (right - left + 1 > k){
                   left++;
                   sum= sum - nums[left-1] + nums[right];
                   res = Math.max(sum , res);
              }
          }
           return (double) res/k;
      }
    }

    3题:

    题解:用两个指针维持一个窗口,right指针遍历数组,对于新来的字符,每次判断窗口是否包含新的字符,如果没有包含就更新最大不含有重复字符的 最长子串 的长度,如果包含,就更新left指针,更新至窗口中相同的字符的下一个。直到整个数组遍历完成。

    class Solution {
    public int lengthOfLongestSubstring(String s) {
    if (s == null || s.length()<1)
    return 0;
    int left = 0;
    int max = 1;
    for (int right = 1; right < s.length(); right++) {
    char c = s.charAt(right);
    for (int i = left; i < right; i++) {
    if (s.charAt(i) == c){
    left = i+1;
    }
    }
    max = Math.max(max, right - left + 1);
    }
    return max;
    }
    }

    1423题:

    题解:滑动窗口,每次拿的时候只能从开头和末尾拿, 而不能从中间拿。也就是说如果把数组的首尾相连, 串成一个环形, 那么最终拿掉的k 个元素肯定是连续的, 问题就转化为求k 个连续元素的最大和。用两个指针维持一个窗口,从数组最右边开始滑动,超过部分就跳到左边来。

    image-20211110101847279

    class Solution {
    public int maxScore(int[] cardPoints, int k) {
    int curMax = 0;
    int left = cardPoints.length - k;//窗口的左边
    for (int i = left; i < cardPoints.length; i++) {
    curMax += cardPoints[i];
    }
    int max = curMax;
    for (int right = 0; right < k; right++) {//窗口的右边
    curMax = curMax - cardPoints[left];
    left++;
    left = left >= cardPoints.length?0:left;//维持窗口的左边
    curMax = curMax + cardPoints[right];//窗口右边有新的元素进来了
    max = Math.max(max , curMax);
    }
    return max;
    }
    }

    76题(困难):

    题解:滑动窗口,比较好理解,使用两个指针left跟right指针, 分别表示窗口的左边界和右边界。

    • 当窗口内的所有字符不能覆盖t 的时候, 要扩大窗口, 也就是right往右移。

    • 当窗口内的所有字符可以覆盖t 的时候, 记录窗口的起始位置以及窗口的长度, 然后缩小窗口 ,left往右移,因为这里求的是能覆盖的最小子串,所以这里还需要判断是否还能覆盖t。如果缩小的窗口还能覆盖t ,保存长度最小的窗口即可。

    重复上面的操作, 直到窗口的右边不能再移动为止。

    class Solution {
    public String minWindow(String s, String t) {

    if (s.length() < t.length())
    return "";

    Map<Character , Integer> map = new HashMap<>();
    for (char c : t.toCharArray()){
    map.put(c , map.getOrDefault(c , 0) + 1);
    }
    int left = 0;
    int len = Integer.MAX_VALUE;
    int start = 0;
    for (int right = 0; right < s.length(); right++) {

    char ch = s.charAt(right);
    if (map.containsKey(ch)){
    map.put(ch , map.get(ch)-1);
    }
    int curLen = right - left + 1;
    while (curLen >= t.length() && isOk(map)){
    //符合条件,更新长度跟开始值
    if (curLen < len){
    len = curLen;
    start = left;
    }
    //开始去掉左值
    char c = s.charAt(left);
    if (map.containsKey(c)){
    map.put(c , map.getOrDefault(c , 0) + 1);
    }
    left++;
    curLen--;
    }

    }
    return len == Integer.MAX_VALUE?"":s.substring(start , start+len);
    }
    private boolean isOk(Map<Character , Integer> map){
    for (char key : map.keySet()){
    if (map.get(key) > 0){
    return false;
    }
    }
    return true;
    }
    }

    57题:

    题解:双指针维持一个滑动窗口,从1到target开始遍历,累计和,如果当前和小于target,窗口右边滑动,加入新的数到窗口中,如果大于,左边滑动,直到小于等于target为止,等于的时候收集解就行。

    image-20211110134708992

    class Solution {
    public int[][] findContinuousSequence(int target) {
    List<int[]> res = new ArrayList<>();
    int sum = 0;
    int left = 1;
    for (int right = 1; right < target; right++) {
    sum += right;
    while (sum > target){
    sum -= left;
    left++;
    }
    if (sum == target){
    int[] arr = new int[right-left+1];
    for (int i = left; i <= right; i++) {
    arr[i-left] = i;
    }
    res.add(arr);
    }
    }
    int[][] ret = new int[res.size()][];
    for (int i = 0; i < res.size(); i++) {
    ret[i] = res.get(i);
    }
    return ret;
    }
    }

    424. 替换后的最长重复字符

    题解:典型的滑动窗口题目,两个指针,left跟right分别指向窗口左右边界,在一个窗口内假如出现次数最多的那个字符出现的次数是a,窗口的长度是b , 只要满足a + k > b , 我们可以把窗口中的其他字符全部替换为出现次数最多的那个字符,这个时候最大长度就是整个窗口的长度,可以继续往窗口加入新的元素(因为题目求的是最长重复字符)。相反如果a + k < b , 我们是没法把窗口内的其他字符全部替换为出现次数最多的那个字符,此时,我们没有办法继续加入新的元素,只能移动left指针,让窗口减小。如此下去,知道窗口右边界right没法移动为止。

    class Solution {
    public int characterReplacement(String s, int k) {
    int[] num = new int[26];
    Arrays.fill(num , 0);
    int left = 0;
    int max = Integer.MIN_VALUE;
    int maxLen = Integer.MIN_VALUE;
    for (int right = 0; right < s.length(); right++) {
    int len = right - left + 1;
    int index = s.charAt(right) - 'A';
    num[index] = num[index] + 1;
    for (int i : num){
    max = Math.max(max , i);
    }
    if (max + k < len){
    index = s.charAt(left) - 'A';
    num[index] = num[index] - 1;
    left++;
    }else {
    maxLen = Math.max(maxLen , len);
    }
    }
    return maxLen;
    }
    }

     

    125. 验证回文串

    题解:使用两个指针, 一个从前开始, 一个从后开始, 两个指针同时往中间走, 如果他们指向的字符不一样, 那么这个字符串肯定不是回文字符串, 直接返回false即可, 如果这两个指针相遇了, 直接返回true。

    class Solution {
    public boolean isPalindrome(String s) {
    s = s.toLowerCase();
    int left = 0;
    int right = s.length()-1;
    while (left < right){
    while (left<right && !Character.isLetterOrDigit(s.charAt(left))){
    left++;
    }
    while (left<right && !Character.isLetterOrDigit(s.charAt(right))){
    right--;
    }
    if (s.charAt(left) != s.charAt(right)){
    return false;
    }
    left++;
    right--;
    }
    return true;
    }
    }

    109. 有序链表转换二叉搜索树

    题解:找到链表的中间节点, 让他成为树的根节点, 中间节点前面进行断开,作为根节点左子树的所有节点, 中间节点后面的就是根节点右子树的所有节点, 然后使用递归的方式再分别对左右子树进行相同的操作就可以

    class Solution {
    public TreeNode sortedListToBST(ListNode head) {

    if (head == null)
    return null;
    if (head.next == null)
    return new TreeNode(head.val);

    ListNode dummyHead = new ListNode(-1);
    dummyHead.next = head;
    ListNode slow = dummyHead;
    ListNode fast = head;
    while (fast != null && fast.next != null){
    slow = slow.next;
    fast = fast.next.next;
    }
    ListNode mid = slow.next;
    slow.next = null;
    TreeNode root = new TreeNode(mid.val);
    root.left = sortedListToBST(head);
    root.right = sortedListToBST(mid.next);
    return root;
    }
    }

     

     

    11. 盛最多水的容器

    题解:双指针,首先说一个比较好理解的,根据木桶原理,容量由最短的木板决定,既然如此,我们首先用left指针指向最左边,right指针指向最右边,先固定left,从后往前遍历right,找到第一个比left大的木板,求得面积更新最大容纳,然后直接left++,让right重新从后往前遍历,不断更新最大容纳,知道left遍历往整个数组,同样的道理,为了防止漏掉,我们也得先固定right,每次让left从前往后遍历,不断更新最大容纳就可以得到最大值。

    class Solution {
    public int maxArea(int[] height) {
    int max = Integer.MIN_VALUE;
    for (int left = 0; left < height.length; left++) {
    for (int right = height.length-1; right >= left; right--) {
    if (height[right] >= height[left]){
    max = Math.max((right - left) * height[left] , max);
    break;
    }
    }
    }
    for (int right = height.length-1; right >= 0; right--) {
    for (int left = 0; left <= right; left++) {
    if (height[left] >= height[right]){
    max = Math.max((right - left) * height[right] , max);
    break;
    }
    }
    }
    return max;
    }
    }

    优化后:同样双指针,我们首先用left指针指向最左边,right指针指向最右边,求得面积,根据木桶原理,容量由最短的木板决定,所以,我们就让木板短的那个往里面靠,尝试寻找更高的,每次都更新面积,最终可以得到最大容量值。

    class Solution {
    public int maxArea(int[] height) {
    int max = Integer.MIN_VALUE;
    int left = 0;
    int right = height.length-1;
    while (left < right){
    if (height[left] < height[right]){
    max = Math.max((right - left) * height[left] , max);
    left++;
    }else {
    max = Math.max((right - left) * height[right] , max);
    right--;
    }
    }
    return max;
    }
    }

    42. 接雨水

    题解:思路很简单,代码稍微有点多。两种情况:

    • 整个数组中只有一个最大值,这种情况比较好处理,找到最大值,从这里分开。

      • 数组开头到最大值处:用两个指针,left跟right,移动right,如果right处的值大于left处的值,有两种情况,一种是连续大于,这种情况left++就行了,因为不可能有积水,如果不是连续大于,那么就从left到right处求出积水和,直到right抵达最大值处。

      • 数组末尾到最大值出:同样的道理求出积水和就行。

    • 整个数组中有多个最大值

      • 这种情况,很好处理,找到其中一个,随便哪个值+1就可以将来整个数组变成只有一个最大值了,然后按照第一种情况进行处理就行。

    public int trap(int[] height) {

    if (height.length < 3){
    return 0;
    }
    int leftIndex = 0;
    int maxValue = height[0];
    for (int i = 1; i < height.length; i++) {
    if (height[i] > maxValue){
    leftIndex = i;
    maxValue = height[i];
    }
    }
    int rightIndex = height.length-1;
    maxValue = height[height.length-1];
    for (int i = height.length-2; i >= 0; i--) {
    if (height[i] > maxValue){
    rightIndex = i;
    maxValue = height[i];
    }
    }
    if (leftIndex != rightIndex){
    height[leftIndex] = height[leftIndex] + 1;
    }
    int sum = 0;
    int left = 0;
    for (int right = 1; right < leftIndex+1; right++) {
    if (height[right] >= height[left]){
    if (right - left > 1){
    //求积水
    for (int i = left + 1; i < right; i++) {
    sum += height[left] - height[i];
    }
    left = right;
    }else {
    left++;
    }
    }
    }
    left = height.length-1;
    for (int right = height.length-2; right >= leftIndex ; right--) {
    if (height[right] >= height[left]){
    if (left - right > 1){
    //求积水
    for (int i = left - 1; i > right; i--) {
    sum += height[left] - height[i];
    }
    left = right;
    }else {
    left--;
    }
    }
    }
    return sum;
    }
  • 相关阅读:
    table表框去掉相邻的间隔
    各种日期格式化返回
    校验金额、大小写字母、大写字母、合法uri、email
    vue js校验金钱、数字
    vue-router 动态添加 路由
    可视化-echarts流向图制作
    HTTP状态码
    二分查找
    编程语言的变量为啥不能是数字开头
    python位运算
  • 原文地址:https://www.cnblogs.com/Swen3252/p/15612754.html
Copyright © 2011-2022 走看看