算法剖析-经典150(双指针、滑动窗口)

打印 上一主题 下一主题

主题 874|帖子 874|积分 2626

双指针

1.验证回文串

1.答案

  1. package com.sunxiansheng.classic150.g1;
  2. /**
  3. * Description: 125. 验证回文串
  4. *
  5. * @Author sun
  6. * @Create 2024/12/19 15:03
  7. * @Version 1.0
  8. */
  9. public class t125 {
  10.     public static boolean isPalindrome(String s) {
  11.         // 直接双指针
  12.         int left = 0;
  13.         int right = s.length() - 1;
  14.         while (left < right) {
  15.             // 获取左右字符
  16.             char leftChar = s.charAt(left);
  17.             char rightChar = s.charAt(right);
  18.             // 如果不是数字和字母,就直接下一位
  19.             if (!Character.isDigit(leftChar) && !Character.isLetter(leftChar)) {
  20.                 left++;
  21.                 continue;
  22.             }
  23.             if (!Character.isDigit(rightChar) && !Character.isLetter(rightChar)) {
  24.                 right--;
  25.                 continue;
  26.             }
  27.             // 大写转换小写
  28.             leftChar = Character.isUpperCase(leftChar) ? Character.toLowerCase(leftChar) : leftChar;
  29.             rightChar = Character.isUpperCase(rightChar) ? Character.toLowerCase(rightChar) : rightChar;
  30.             // 到这里左右都是数字字母字符了,可以直接比较
  31.             if (leftChar != rightChar) {
  32.                 return false;
  33.             }
  34.             left++;
  35.             right--;
  36.         }
  37.         return true;
  38.     }
  39.     public static void main(String[] args) {
  40.         System.out.println("isPalindrome("A man, a plan, a canal: Panama") = " + isPalindrome("A man, a plan, a canal: Panama"));
  41.     }
  42. }
复制代码
2.思路

就是直接利用双指针,如果不是数字和字母,就直接下一位,大写转换小写,然后比较即可
2.判断子序列

1.动态规划解法

  1. package com.sunxiansheng.classic150.g1;
  2. /**
  3. * Description: 392. 判断子序列
  4. *
  5. * @Author sun
  6. * @Create 2024/12/21 09:36
  7. * @Version 1.0
  8. */
  9. public class t392 {
  10.     public boolean isSubsequence(String s, String t) {
  11.         // dp[i][j]:以i-1,j-1为结尾的最长公共子序列的长度
  12.         // 状态转移公式:相同时 dp[i][j] = dp[i - 1][j - 1] + 1 不同时 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
  13.         // 初始化
  14.         int m = s.length();
  15.         int n = t.length();
  16.         int[][] dp = new int[m + 1][n + 1];
  17.         int max = 0;
  18.         // 填充dp数组
  19.         for (int i = 1; i <= m; i++) {
  20.             for (int j = 1; j <= n; j++) {
  21.                 if (s.charAt(i - 1) == t.charAt(j - 1)) {
  22.                     dp[i][j] = dp[i - 1][j - 1] + 1;
  23.                 } else {
  24.                     dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
  25.                 }
  26.                 max = Math.max(max, dp[i][j]);
  27.             }
  28.         }
  29.         return max ==  s.length();
  30.     }
  31. }
复制代码
2.双指针

  1. package com.sunxiansheng.classic150.g1;
  2. /**
  3. * Description: 392. 判断子序列
  4. *
  5. * @Author sun
  6. * @Create 2024/12/21 09:36
  7. * @Version 1.0
  8. */
  9. public class t392 {
  10.     public static boolean isSubsequence(String s, String t) {
  11.         // 双指针
  12.         int left = 0;
  13.         for (int right = 0; right < t.length(); right++) {
  14.             // 如果左数组已经遍历完了就提前退出
  15.             if (left >= s.length()) {
  16.                 break;
  17.             }
  18.             // 当右指针遇到了左指针指向的元素,左指针++
  19.             if (t.charAt(right) == s.charAt(left)) {
  20.                 left++;
  21.             }
  22.         }
  23.         return left >= s.length();
  24.     }
  25. }
复制代码
3.两数之和 II - 输入有序数组

1.答案

  1. package com.sunxiansheng.classic150.g1;
  2. /**
  3. * Description: 167. 两数之和 II - 输入有序数组
  4. *
  5. * @Author sun
  6. * @Create 2024/12/21 10:08
  7. * @Version 1.0
  8. */
  9. public class t167 {
  10.     public int[] twoSum(int[] numbers, int target) {
  11.         // 两个指针从两边往中间移动
  12.         int left = 0;
  13.         int right = numbers.length - 1;
  14.         while (left < right) {
  15.             // 求和
  16.             int sum = numbers[left] + numbers[right];
  17.             // 如果大于target,就右指针移动,小于target就左指针移动
  18.             if (sum > target) {
  19.                 right--;
  20.             } else if (sum < target) {
  21.                 left++;
  22.             } else {
  23.                 return new int[]{left + 1, right + 1};
  24.             }
  25.         }
  26.         return null;
  27.     }
  28. }
复制代码
2.思路

这里用的是贪心双指针,就是两个指针从双方往中间移动,和如果大于target,就右指针移动,小于target就左指针移动
4.盛最多水的容器

1.答案

  1. package com.sunxiansheng.classic150.g1;
  2. /**
  3. * Description: 11. 盛最多水的容器
  4. *
  5. * @Author sun
  6. * @Create 2024/12/21 10:42
  7. * @Version 1.0
  8. */
  9. public class t11 {
  10.     public static int maxArea(int[] height) {
  11.         int max = 0;
  12.         int left = 0, right = height.length - 1;
  13.         while (left < right) {
  14.             // 求水量
  15.             int water = Math.min(height[left], height[right]) * (right - left);
  16.             // 更新最大值
  17.             max = Math.max(max, water);
  18.             // 哪边低移动哪边的指针
  19.             if (height[left] < height[right]) {
  20.                 left++;
  21.             } else {
  22.                 right--;
  23.             }
  24.         }
  25.         return max;
  26.     }
  27. }
复制代码
2.思路

利用贪心双指针解决
5.三数之和

1.答案

  1. package com.sunxiansheng.classic150.g1;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. /**
  6. * Description: 15. 三数之和
  7. *
  8. * @Author sun
  9. * @Create 2024/12/21 10:50
  10. * @Version 1.0
  11. */
  12. public class t15 {
  13.     public static List<List<Integer>> threeSum(int[] nums) {
  14.         // 使用遍历+贪心双指针来解决
  15.         List<List<Integer>> res = new ArrayList<>();
  16.         // 如果数组长度小于3,直接返回空结果
  17.         if (nums == null || nums.length < 3) {
  18.             return res;
  19.         }
  20.         // 首先排序
  21.         Arrays.sort(nums);
  22.         // 遍历
  23.         for (int i = 0; i < nums.length - 2; i++) {
  24.             // 关键点:当前的元素跟前一个元素是相同的情况下不必考虑
  25.             if (i > 0 && nums[i] == nums[i - 1]) {
  26.                 continue;
  27.             }
  28.             // 贪心双指针
  29.             int left = i + 1;
  30.             int right = nums.length - 1;
  31.             while (left < right) {
  32.                 // 求结果
  33.                 int sum = nums[left] + nums[right] + nums[i];
  34.                 // 根据结果来贪心的调整状态
  35.                 if (sum > 0) {
  36.                     right--;
  37.                 } else if (sum < 0) {
  38.                     left++;
  39.                 } else {
  40.                     res.add(Arrays.asList(nums[i], nums[left], nums[right]));
  41.                     // 关键点:当左右指针指向的下一个元素跟当前元素是相同的也不用考虑
  42.                     while (left < right && nums[left] == nums[left + 1]) {
  43.                         left++;
  44.                     }
  45.                     while (left < right && nums[right] == nums[right - 1]) {
  46.                         right--;
  47.                     }
  48.                     left++;
  49.                     right--;
  50.                 }
  51.             }
  52.         }
  53.         return res;
  54.     }
  55. }
复制代码
2.思路

利用遍历+贪心双指针来解决,注意两个关键点一个是在遍历时,遇到重复元素就跳过,另一个是在左右指针移动时,遇到重复元素也跳过
滑动窗口

1.长度最小的子数组

1.答案

  1. package com.sunxiansheng.classic150.g1;
  2. /**
  3. * Description: 209. 长度最小的子数组
  4. *
  5. * @Author sun
  6. * @Create 2024/12/21 11:20
  7. * @Version 1.0
  8. */
  9. public class t209 {
  10.     public static int minSubArrayLen(int target, int[] nums) {
  11.         // 滑动窗口定义:窗口内的元素要小于target
  12.         int left = 0;
  13.         int res = Integer.MAX_VALUE;
  14.         int sum = 0;
  15.         for (int right = 0; right < nums.length; right++) {
  16.             // 加入窗口
  17.             sum += nums[right];
  18.             // 只要窗口内的元素是大于target的,就进行处理
  19.             while (sum >= target) {
  20.                 // 计算结果
  21.                 res = Math.min(res, right - left + 1);
  22.                 // 滑动窗口
  23.                 sum -= nums[left++];
  24.             }
  25.         }
  26.         return res == Integer.MAX_VALUE ? 0 : res;
  27.     }
  28. }
复制代码
2.思路

先辈行滑动窗口的界说窗口内的元素要小于target,那么只要窗口内的元素是大于target的,就进行处理,就可以盘算结果了
2.无重复字符的最长子串

1.答案

  1. package com.sunxiansheng.classic150.g1;
  2. import java.util.HashSet;
  3. import java.util.Set;
  4. /**
  5. * Description: 3. 无重复字符的最长子串
  6. *
  7. * @Author sun
  8. * @Create 2024/12/21 11:28
  9. * @Version 1.0
  10. */
  11. public class t3 {
  12.     public static int lengthOfLongestSubstring(String s) {
  13.         // 滑动窗口定义:窗口内的元素不能重复
  14.         int left = 0;
  15.         int res = 0;
  16.         Set<Character> set = new HashSet<>();
  17.         for (int right = 0; right < s.length(); right++) {
  18.             // 获取set的长度
  19.             int before = set.size();
  20.             // 加入窗口
  21.             set.add(s.charAt(right));
  22.             res = Math.max(res, set.size());
  23.             // 当窗口内的元素重复时
  24.             if (set.size() == before) {
  25.                 while (s.charAt(left) != s.charAt(right)) {
  26.                     // 滑动窗口
  27.                     set.remove(s.charAt(left));
  28.                     left++;
  29.                 }
  30.                 // 到这里就说明当前left指向了那个重复元素,继续滑动窗口,不过不需要移动set
  31.                 left++;
  32.             }
  33.         }
  34.         return res;
  35.     }
  36. }
复制代码
2.思路

这里面比较复杂一点,十分考察对滑动窗口算法的明白,加入窗口前先要获取一下加入set之前的长度,如果加入窗口后长度不变,那么就一定是元素重复了,需要滑动窗口直到不重复。盘算结果则是在加入窗口的时候
3.最小覆盖子串

1.答案

  1. package com.sunxiansheng.classic150.g1;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. /**
  5. * Description: 76. 最小覆盖子串
  6. *
  7. * @Author sun
  8. * @Create 2024/12/21 14:19
  9. * @Version 1.0
  10. */
  11. public class t76 {
  12.     public static String minWindow(String s, String t) {
  13.         // 滑动窗口定义:窗口内元素不能全部包含t的所有元素
  14.         // 使用map来记录t的词频
  15.         Map<Character, Integer> tMap = new HashMap<>();
  16.         for (int i = 0; i < t.length(); i++) {
  17.             tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i), 0) + 1);
  18.         }
  19.         // 所有的字符数(当满足条件的字符数为n时,表明该子串就是覆盖子串了)
  20.         int n = t.length();
  21.         // 满足条件的字符数
  22.         int satisfy = 0;
  23.         // s的词频
  24.         Map<Character, Integer> sMap = new HashMap<>();
  25.         // 最小覆盖子串的长度
  26.         int minSonLength = Integer.MAX_VALUE;
  27.         // 最小覆盖子串
  28.         String minSon = "";
  29.         int left = 0;
  30.         for (int right = 0; right < s.length(); right++) {
  31.             // 获取到目标字符
  32.             char c = s.charAt(right);
  33.             // 只考虑是t的元素的情况
  34.             if (tMap.containsKey(c)) {
  35.                 // 加入窗口
  36.                 sMap.put(c, sMap.getOrDefault(c, 0) + 1);
  37.                 // 如果目标字符的出现次数是小于t中的频率,就可以增加满足条件的字符数
  38.                 if (sMap.get(c) <= tMap.get(c)) {
  39.                     satisfy++;
  40.                 }
  41.             }
  42.             // 只要目前是覆盖子串,就需要滑动窗口
  43.             while (satisfy == n) {
  44.                 // 更新最小覆盖子串
  45.                 if ((right - left + 1) < minSonLength) {
  46.                     minSon = s.substring(left, right + 1);
  47.                     minSonLength = right - left + 1;
  48.                 }
  49.                 // 滑动窗口
  50.                 // 当是t的元素时
  51.                 if (tMap.containsKey(s.charAt(left))) {
  52.                     // 更新词频和满足的字符数
  53.                     sMap.put(s.charAt(left), sMap.get(s.charAt(left)) - 1);
  54.                     // 只有当窗口中的元素减少后,确实比t中的词频少了,才会减少满足的字符数
  55.                     if (sMap.get(s.charAt(left)) < tMap.get(s.charAt(left))) {
  56.                         satisfy--;
  57.                     }
  58.                 }
  59.                 // 不是t的元素就直接滑动即可
  60.                 left++;
  61.             }
  62.         }
  63.         return minSon;
  64.     }
  65.     public static void main(String[] args) {
  66.         minWindow("ADOBECODEBANC", "ABC");
  67.     }
  68. }
复制代码
2.思路

焦点就是利用Map来记录两个字符串的词频,然后当满意条件的字符数量为t的个数时就是覆盖子串


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表