去重压缩

1047-删除字符串中的所有相邻重复项

image.png
题意分析:
解题思路:

  • 栈实现。即将入栈元素与栈顶元素进行对比。
  • StringBuilder实现。top 指针指向 sb 的‘栈’顶位置。

注意点:
代码:

  1. public String removeDuplicates(String s) {
  2. if (s.length() < 2) {
  3. return s;
  4. }
  5. StringBuilder sb = new StringBuilder();
  6. int top = -1;
  7. for (int i = 0; i < s.length(); i++) {
  8. char ch = s.charAt(i);
  9. if (top >= 0 && sb.charAt(top) == ch) {
  10. sb.deleteCharAt(top);
  11. top--;
  12. } else {
  13. sb.append(ch);
  14. top++;
  15. }
  16. }
  17. return sb.toString();
  18. }

*1209-删除字符串中的所有相邻重复项II

image.png
题意分析:
解题思路:
注意点:
代码:

  1. /**
  2. * 用辅助数组记录所有字符位置出现的次数,重复则倒退。
  3. * @param s
  4. * @param k
  5. * @return
  6. */
  7. public String removeDuplicates(String s, int k) {
  8. StringBuilder sb = new StringBuilder(s);
  9. // 记录元素连续出现次数
  10. int[] cnts = new int[s.length()];
  11. for (int i = 0; i < sb.length(); i++) {
  12. if (i == 0 || sb.charAt(i) != sb.charAt(i-1)) {
  13. cnts[i] = 1;
  14. } else {
  15. cnts[i] = cnts[i-1] + 1;
  16. if (cnts[i] == k) {
  17. sb.delete(i-k+1, i+1);
  18. i = i - k;
  19. }
  20. }
  21. }
  22. return sb.toString();
  23. }
  24. /**
  25. * 用辅助栈单独记录连续元素出现的次数,重复则倒退。
  26. * @param s
  27. * @param k
  28. * @return
  29. */
  30. public String removeDuplicates2(String s, int k) {
  31. StringBuilder sb = new StringBuilder(s);
  32. // 记录元素连续出现次数
  33. Stack<Integer> stack = new Stack<>();
  34. for (int i = 0; i < sb.length(); i++) {
  35. if (i == 0 || sb.charAt(i) != sb.charAt(i-1)) {
  36. stack.push(1);
  37. } else {
  38. stack.push(stack.pop() + 1);
  39. if (stack.peek() == k) {
  40. // sb删除重复的k个元素,然后更新i位置继续遍历。
  41. sb.delete(i-k+1, i+1);
  42. i = i - k;
  43. // 栈中等于k的元素出栈
  44. stack.pop();
  45. }
  46. }
  47. }
  48. return sb.toString();
  49. }

*443-压缩字符串

image.png
题意分析:

  • 思路有点像删除数组中的重复元素,边扫描边更新历史字符,并记录重复字符的长度。

解题思路:

  • 双指针原地更新。read 和 write 指针同时走动,read 指针负责向前读取字符,write 指针更新字符扫描记录。
    • 时间复杂度:O(n)。
    • 空间复杂度:O(1)。

注意点:

  • 字符如果出现一次则不需要记录次数,超过十次需要分别每个元素的值。

代码:

  1. /**
  2. * 双指针。读指针和写指针,read指针不停扫描,write指针更新数据
  3. * @param chars
  4. * @return
  5. */
  6. public int compress(char[] chars) {
  7. int n = chars.length;
  8. int read = 0, write = 0;
  9. // 记录一个字符的起始位置
  10. int start = 0;
  11. for (read = 0; read < n; read++) {
  12. // 判断字符变化的 read 位置,以及 read 指针是否走到了最后一个节点
  13. if (read == n-1 || chars[read] != chars[read+1]) {
  14. chars[write++] = chars[read];
  15. int nums = read - start + 1;
  16. if (nums > 1) {
  17. // sb用来记录重复字符的数量
  18. StringBuffer sb = new StringBuffer();
  19. while (nums > 0) {
  20. sb.append(nums % 10);
  21. nums /= 10;
  22. }
  23. // System.out.println("sb = " + sb.toString());
  24. // 将 sb 中的数量存在在 chars 数据中
  25. for (int i = sb.length() - 1; i >= 0; i--) {
  26. chars[write++] = sb.charAt(i);
  27. }
  28. }
  29. // 更新元素第一次出现的位置
  30. start = read + 1;
  31. }
  32. }
  33. // System.out.println(Arrays.toString(chars));
  34. // 求解,write指针是记录压缩后的字符位置
  35. return write;
  36. }

滑动窗口系列

3-无重复字符的最长字串

image.png
题意分析:
解题思路:
注意点:
代码:

  1. /**
  2. * Hash记录重复字符出现位置
  3. */
  4. public int lengthOfLongestSubstring(String s) {
  5. int i = 0, j = 0;
  6. Map<Character, Integer> map = new HashMap<>();
  7. int len = 0;
  8. for (j = 0; j < s.length(); j++) {
  9. if (map.containsKey(s.charAt(j))) {
  10. // i 起始值不能回退
  11. i = Math.max(map.get(s.charAt(j)) + 1, i);
  12. }
  13. len = Math.max(len, j - i + 1);
  14. map.put(s.charAt(j), j);
  15. }
  16. return len;
  17. }
  18. /**
  19. * 滑动窗口。"abcabcbb"
  20. */
  21. public int lengthOfLongestSubstring2(String s) {
  22. int left = 0, right = 0;
  23. int len = 0;
  24. Map<Character, Integer> window = new HashMap<>();
  25. while (right < s.length()) {
  26. char ch = s.charAt(right);
  27. right++;
  28. if (window.containsKey(ch))
  29. window.put(ch, window.get(ch) + 1);
  30. else
  31. window.put(ch, 1);
  32. // 更新左窗口 left
  33. while (window.get(ch) > 1) {
  34. char chdel = s.charAt(left);
  35. left++;
  36. window.put(chdel, window.get(chdel) - 1);
  37. }
  38. len = Math.max(len, right - left);
  39. }
  40. return len;
  41. }

567-字符串的排列

image.png
题意分析:
解题思路:

  • 滑动窗口。

注意点:
代码:

  1. public boolean checkInclusion(String s1, String s2) {
  2. String str = s2, substr = s1;
  3. int left = 0, right = 0;
  4. Map<Character, Integer> window = new HashMap<>();
  5. Map<Character, Integer> need = new HashMap<>();
  6. // 关键变量,记录 window 中数据是否包含 need 的元素
  7. int valid = 0;
  8. for (int i = 0; i < substr.length(); i++) {
  9. char ch = substr.charAt(i);
  10. if (need.containsKey(ch)) need.put(ch, need.get(ch) + 1); else need.put(ch, 1);
  11. }
  12. while (right < str.length()) {
  13. char chadd = str.charAt(right);
  14. right++;
  15. // 更新窗口数据
  16. if (need.containsKey(chadd)) {
  17. if (window.containsKey(chadd)) window.put(chadd, window.get(chadd) + 1); else window.put(chadd, 1);
  18. if (window.get(chadd).equals(need.get(chadd))) {
  19. valid++;
  20. }
  21. }
  22. // 缩小窗口
  23. while (valid == need.size()) {
  24. if (right - left == substr.length()) {
  25. return true;
  26. }
  27. char chdel = str.charAt(left);
  28. left++;
  29. // 更新窗口数据
  30. if (need.containsKey(chdel)) {
  31. if (window.get(chdel).equals(need.get(chdel))) {
  32. valid--;
  33. }
  34. window.put(chdel, window.get(chdel) -1);
  35. }
  36. }
  37. }
  38. return false;
  39. }

438-找到字符串中所有字母异位词

image.png
题意分析:
解题思路:
注意点:
代码:

  1. public List<Integer> findAnagrams(String s, String p) {
  2. String str = s, substr = p;
  3. Map<Character, Integer> window = new HashMap<>(), need = new HashMap<>();
  4. for (int i = 0; i< substr.length(); i++) {
  5. char ch = substr.charAt(i);
  6. if (need.containsKey(ch)) need.put(ch, need.get(ch) + 1); else need.put(ch, 1);
  7. }
  8. int left = 0, right = 0;
  9. // 记录是否是可行解
  10. int valid = 0;
  11. List<Integer> list = new ArrayList<>();
  12. while (right < str.length()) {
  13. char chadd = str.charAt(right);
  14. right++;
  15. // 记录「可行解」
  16. if (need.containsKey(chadd)) {
  17. if (window.containsKey(chadd)) window.put(chadd, window.get(chadd) + 1); else window.put(chadd, 1);
  18. if (window.get(chadd).equals(need.get(chadd))) {
  19. valid++;
  20. }
  21. }
  22. // 缩小窗口
  23. while (valid == need.size()) {
  24. // 判断是否要更新结果
  25. if (right - left == substr.length()) {
  26. list.add(left);
  27. }
  28. char chdel = str.charAt(left);
  29. left++;
  30. // 更新「可行解」
  31. if (need.containsKey(chdel)) {
  32. if (window.get(chdel).equals(need.get(chdel))) {
  33. valid--;
  34. }
  35. window.put(chdel, window.get(chdel) - 1);
  36. }
  37. }
  38. }
  39. return list;
  40. }

76-最小覆盖子串

image.png
题意分析:
解题思路:
注意点:
代码:

  1. public String minWindow(String s, String t) {
  2. String str = s, substr = t;
  3. Map<Character, Integer> window = new HashMap<>(), need = new HashMap<>();
  4. for (int i = 0; i< substr.length(); i++) {
  5. char ch = substr.charAt(i);
  6. if (need.containsKey(ch)) need.put(ch, need.get(ch) + 1); else need.put(ch, 1);
  7. }
  8. int left = 0, right = 0;
  9. // 记录是否是可行解
  10. int valid = 0;
  11. int start = -1;
  12. int len = Integer.MAX_VALUE;
  13. while (right < str.length()) {
  14. char chadd = str.charAt(right);
  15. right++;
  16. // 记录「可行解」
  17. if (need.containsKey(chadd)) {
  18. if (window.containsKey(chadd)) window.put(chadd, window.get(chadd) + 1); else window.put(chadd, 1);
  19. if (window.get(chadd).equals(need.get(chadd))) {
  20. valid++;
  21. }
  22. }
  23. // 缩小窗口
  24. while (valid == need.size()) {
  25. // 判断是否要更新结果
  26. if (right - left < len) {
  27. start = left;
  28. len = right - left;
  29. }
  30. char chdel = str.charAt(left);
  31. left++;
  32. // 更新「可行解」
  33. if (need.containsKey(chdel)) {
  34. if (window.get(chdel).equals(need.get(chdel))) {
  35. valid--;
  36. }
  37. window.put(chdel, window.get(chdel) - 1);
  38. }
  39. }
  40. }
  41. System.out.println("start=" + start + "; len = " + len);
  42. return (start == -1) ? "" : str.substring(start, start+len);
  43. }

未分类

*1081-不同字符的最小子序列

image.png
题意分析:

  • 去掉字符串中的重复字符,并保证字符的相对顺序,且去重后在字典序中序列最小。

解题思路:

  • 三步曲。
    • 去重。
    • 去重后字符相对顺序不能打乱。(单调递减栈)
    • 字典序列最小。(这一点最难)

注意点:

  • 记录每个字符次数 HashMap。
  • 记录每个字符是否被访问过。 isAccessed = new boolean[size]。这里是以 ASCII 字符作为索引的 size 要好好理解。

代码:

  1. /**
  2. * 解题思路
  3. * 1、字符去重
  4. * 2、字符子顺序不变
  5. * 3、字典序最小
  6. * @param s
  7. * @return
  8. */
  9. public String smallestSubsequence(String s) {
  10. // 用栈保存遍历字符串的字符
  11. Stack<Character> stack = new Stack<>();
  12. // 记录字符是否已经存在
  13. boolean[] isExisted = new boolean[s.length()];
  14. // 记录每个字符出现的次数(求最小字典序时会用到)
  15. Map<Character, Integer> cntMap = new HashMap<>();
  16. // 初始化字符出现次数
  17. for (char ch : s.toCharArray()) {
  18. if (!cntMap.containsKey(ch))
  19. cntMap.put(ch, 1);
  20. else
  21. cntMap.put(ch, cntMap.get(ch) + 1);
  22. }
  23. // 更新栈
  24. for (char ch : s.toCharArray()) {
  25. // 每遍历一个字符对应的计数减 1。(后续串中该元素还剩多少个)
  26. cntMap.put(ch, cntMap.get(ch) - 1);
  27. // 重复字符不处理
  28. if (isExisted[ch - 'a'] == true) continue;
  29. // 重点是处理入栈的小字符
  30. while (!stack.isEmpty() && ch < stack.peek()) {
  31. if (cntMap.get(stack.peek()) == 0)
  32. break;
  33. isExisted[stack.pop() - 'a'] = false;
  34. }
  35. // 常规入栈并标记动作
  36. stack.push(ch);
  37. isExisted[ch - 'a'] = true;
  38. // System.out.println(isExisted.length + " : " + Arrays.toString(isExisted));
  39. }
  40. // 出栈字符与实际字符反向
  41. StringBuilder sb = new StringBuilder();
  42. while (!stack.isEmpty()) {
  43. sb.append(stack.pop());
  44. }
  45. return sb.reverse().toString();
  46. }

171-Excel 表列序号image.png

题意分析:
解题思路:
注意点:
代码:

  1. public int titleToNumber(String columnTitle) {
  2. int ret = 0;
  3. for (int i = 0; i < columnTitle.length(); i++) {
  4. char ch = columnTitle.charAt(i);
  5. int val = ch - 'A' + 1;
  6. ret += val * Math.pow(26, columnTitle.length() - i -1);
  7. }
  8. return ret;
  9. }

415-字符串相加(大数相加)

image.png
题意分析:

  • 两个大数以字符串的形式做加法。

解题思路:

  • 按位计算,个位+个位,十位+十位……
    • 时间复杂度:max(O(m), O(n))

注意点:
扩展:

  • 多个大数相加如何处理,超过 INT 最大值如何处理,如果有负数又如何处理。

代码:

  1. /**
  2. * 解题思路:按位相加,如何处理 sum 和 carry
  3. *
  4. * 扩展:
  5. * 1、如果处理多个字符串的相加
  6. * 2、如果相加中包含负数该如何处理
  7. * 3、相加后如何判断超过 Int 类型限制(大数相加)
  8. * @param num1
  9. * @param num2
  10. * @return
  11. */
  12. public String addTwoStrings(String num1, String num2) {
  13. int i = num1.length() - 1, j = num2.length() - 1;
  14. StringBuilder sb = new StringBuilder();
  15. int carry = 0, sum = 0;
  16. while (i >= 0 || j >= 0) {
  17. // 双指针按位相加,如果没有位则按0值处理
  18. int a = i >= 0 ? num1.charAt(i) - '0' : 0;
  19. int b = j >= 0 ? num2.charAt(j) - '0' : 0;
  20. // System.out.println("a = " + a + "; b = " + b);
  21. sum = a + b + carry;
  22. carry = sum / 10;
  23. sb.append(sum % 10);
  24. i--;
  25. j--;
  26. }
  27. if (carry == 1) sb.append(carry);
  28. return sb.reverse().toString();
  29. }
  30. /**
  31. * 多个字符串相加
  32. * @param strs
  33. * @return
  34. */
  35. public String addMultiStrings(String[] strs) {
  36. if (strs.length == 1) return strs[0];
  37. String twoSum = addTwoStrings(strs[0] , "0");
  38. System.out.println("first = " + twoSum);
  39. for (int i = 1; i < strs.length; i++) {
  40. twoSum = addTwoStrings(strs[i], twoSum);
  41. System.out.println("iter twoSum = " + twoSum);
  42. }
  43. return twoSum;
  44. }

模版

题意分析:
解题思路:
注意点:
扩展:
代码: