去重压缩
1047-删除字符串中的所有相邻重复项
题意分析:
解题思路:
- 栈实现。即将入栈元素与栈顶元素进行对比。
- StringBuilder实现。top 指针指向 sb 的‘栈’顶位置。
注意点:
代码:
public String removeDuplicates(String s) {
if (s.length() < 2) {
return s;
}
StringBuilder sb = new StringBuilder();
int top = -1;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (top >= 0 && sb.charAt(top) == ch) {
sb.deleteCharAt(top);
top--;
} else {
sb.append(ch);
top++;
}
}
return sb.toString();
}
*1209-删除字符串中的所有相邻重复项II
题意分析:
解题思路:
注意点:
代码:
/**
* 用辅助数组记录所有字符位置出现的次数,重复则倒退。
* @param s
* @param k
* @return
*/
public String removeDuplicates(String s, int k) {
StringBuilder sb = new StringBuilder(s);
// 记录元素连续出现次数
int[] cnts = new int[s.length()];
for (int i = 0; i < sb.length(); i++) {
if (i == 0 || sb.charAt(i) != sb.charAt(i-1)) {
cnts[i] = 1;
} else {
cnts[i] = cnts[i-1] + 1;
if (cnts[i] == k) {
sb.delete(i-k+1, i+1);
i = i - k;
}
}
}
return sb.toString();
}
/**
* 用辅助栈单独记录连续元素出现的次数,重复则倒退。
* @param s
* @param k
* @return
*/
public String removeDuplicates2(String s, int k) {
StringBuilder sb = new StringBuilder(s);
// 记录元素连续出现次数
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < sb.length(); i++) {
if (i == 0 || sb.charAt(i) != sb.charAt(i-1)) {
stack.push(1);
} else {
stack.push(stack.pop() + 1);
if (stack.peek() == k) {
// sb删除重复的k个元素,然后更新i位置继续遍历。
sb.delete(i-k+1, i+1);
i = i - k;
// 栈中等于k的元素出栈
stack.pop();
}
}
}
return sb.toString();
}
*443-压缩字符串
题意分析:
- 思路有点像删除数组中的重复元素,边扫描边更新历史字符,并记录重复字符的长度。
解题思路:
- 双指针原地更新。read 和 write 指针同时走动,read 指针负责向前读取字符,write 指针更新字符扫描记录。
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
注意点:
- 字符如果出现一次则不需要记录次数,超过十次需要分别每个元素的值。
代码:
/**
* 双指针。读指针和写指针,read指针不停扫描,write指针更新数据
* @param chars
* @return
*/
public int compress(char[] chars) {
int n = chars.length;
int read = 0, write = 0;
// 记录一个字符的起始位置
int start = 0;
for (read = 0; read < n; read++) {
// 判断字符变化的 read 位置,以及 read 指针是否走到了最后一个节点
if (read == n-1 || chars[read] != chars[read+1]) {
chars[write++] = chars[read];
int nums = read - start + 1;
if (nums > 1) {
// sb用来记录重复字符的数量
StringBuffer sb = new StringBuffer();
while (nums > 0) {
sb.append(nums % 10);
nums /= 10;
}
// System.out.println("sb = " + sb.toString());
// 将 sb 中的数量存在在 chars 数据中
for (int i = sb.length() - 1; i >= 0; i--) {
chars[write++] = sb.charAt(i);
}
}
// 更新元素第一次出现的位置
start = read + 1;
}
}
// System.out.println(Arrays.toString(chars));
// 求解,write指针是记录压缩后的字符位置
return write;
}
滑动窗口系列
3-无重复字符的最长字串
题意分析:
解题思路:
注意点:
代码:
/**
* Hash记录重复字符出现位置
*/
public int lengthOfLongestSubstring(String s) {
int i = 0, j = 0;
Map<Character, Integer> map = new HashMap<>();
int len = 0;
for (j = 0; j < s.length(); j++) {
if (map.containsKey(s.charAt(j))) {
// i 起始值不能回退
i = Math.max(map.get(s.charAt(j)) + 1, i);
}
len = Math.max(len, j - i + 1);
map.put(s.charAt(j), j);
}
return len;
}
/**
* 滑动窗口。"abcabcbb"
*/
public int lengthOfLongestSubstring2(String s) {
int left = 0, right = 0;
int len = 0;
Map<Character, Integer> window = new HashMap<>();
while (right < s.length()) {
char ch = s.charAt(right);
right++;
if (window.containsKey(ch))
window.put(ch, window.get(ch) + 1);
else
window.put(ch, 1);
// 更新左窗口 left
while (window.get(ch) > 1) {
char chdel = s.charAt(left);
left++;
window.put(chdel, window.get(chdel) - 1);
}
len = Math.max(len, right - left);
}
return len;
}
567-字符串的排列
题意分析:
解题思路:
- 滑动窗口。
注意点:
代码:
public boolean checkInclusion(String s1, String s2) {
String str = s2, substr = s1;
int left = 0, right = 0;
Map<Character, Integer> window = new HashMap<>();
Map<Character, Integer> need = new HashMap<>();
// 关键变量,记录 window 中数据是否包含 need 的元素
int valid = 0;
for (int i = 0; i < substr.length(); i++) {
char ch = substr.charAt(i);
if (need.containsKey(ch)) need.put(ch, need.get(ch) + 1); else need.put(ch, 1);
}
while (right < str.length()) {
char chadd = str.charAt(right);
right++;
// 更新窗口数据
if (need.containsKey(chadd)) {
if (window.containsKey(chadd)) window.put(chadd, window.get(chadd) + 1); else window.put(chadd, 1);
if (window.get(chadd).equals(need.get(chadd))) {
valid++;
}
}
// 缩小窗口
while (valid == need.size()) {
if (right - left == substr.length()) {
return true;
}
char chdel = str.charAt(left);
left++;
// 更新窗口数据
if (need.containsKey(chdel)) {
if (window.get(chdel).equals(need.get(chdel))) {
valid--;
}
window.put(chdel, window.get(chdel) -1);
}
}
}
return false;
}
438-找到字符串中所有字母异位词
题意分析:
解题思路:
注意点:
代码:
public List<Integer> findAnagrams(String s, String p) {
String str = s, substr = p;
Map<Character, Integer> window = new HashMap<>(), need = new HashMap<>();
for (int i = 0; i< substr.length(); i++) {
char ch = substr.charAt(i);
if (need.containsKey(ch)) need.put(ch, need.get(ch) + 1); else need.put(ch, 1);
}
int left = 0, right = 0;
// 记录是否是可行解
int valid = 0;
List<Integer> list = new ArrayList<>();
while (right < str.length()) {
char chadd = str.charAt(right);
right++;
// 记录「可行解」
if (need.containsKey(chadd)) {
if (window.containsKey(chadd)) window.put(chadd, window.get(chadd) + 1); else window.put(chadd, 1);
if (window.get(chadd).equals(need.get(chadd))) {
valid++;
}
}
// 缩小窗口
while (valid == need.size()) {
// 判断是否要更新结果
if (right - left == substr.length()) {
list.add(left);
}
char chdel = str.charAt(left);
left++;
// 更新「可行解」
if (need.containsKey(chdel)) {
if (window.get(chdel).equals(need.get(chdel))) {
valid--;
}
window.put(chdel, window.get(chdel) - 1);
}
}
}
return list;
}
76-最小覆盖子串
题意分析:
解题思路:
注意点:
代码:
public String minWindow(String s, String t) {
String str = s, substr = t;
Map<Character, Integer> window = new HashMap<>(), need = new HashMap<>();
for (int i = 0; i< substr.length(); i++) {
char ch = substr.charAt(i);
if (need.containsKey(ch)) need.put(ch, need.get(ch) + 1); else need.put(ch, 1);
}
int left = 0, right = 0;
// 记录是否是可行解
int valid = 0;
int start = -1;
int len = Integer.MAX_VALUE;
while (right < str.length()) {
char chadd = str.charAt(right);
right++;
// 记录「可行解」
if (need.containsKey(chadd)) {
if (window.containsKey(chadd)) window.put(chadd, window.get(chadd) + 1); else window.put(chadd, 1);
if (window.get(chadd).equals(need.get(chadd))) {
valid++;
}
}
// 缩小窗口
while (valid == need.size()) {
// 判断是否要更新结果
if (right - left < len) {
start = left;
len = right - left;
}
char chdel = str.charAt(left);
left++;
// 更新「可行解」
if (need.containsKey(chdel)) {
if (window.get(chdel).equals(need.get(chdel))) {
valid--;
}
window.put(chdel, window.get(chdel) - 1);
}
}
}
System.out.println("start=" + start + "; len = " + len);
return (start == -1) ? "" : str.substring(start, start+len);
}
未分类
*1081-不同字符的最小子序列
题意分析:
- 去掉字符串中的重复字符,并保证字符的相对顺序,且去重后在字典序中序列最小。
解题思路:
- 三步曲。
- 去重。
- 去重后字符相对顺序不能打乱。(单调递减栈)
- 字典序列最小。(这一点最难)
注意点:
- 记录每个字符次数 HashMap。
- 记录每个字符是否被访问过。 isAccessed = new boolean[size]。这里是以 ASCII 字符作为索引的 size 要好好理解。
代码:
/**
* 解题思路
* 1、字符去重
* 2、字符子顺序不变
* 3、字典序最小
* @param s
* @return
*/
public String smallestSubsequence(String s) {
// 用栈保存遍历字符串的字符
Stack<Character> stack = new Stack<>();
// 记录字符是否已经存在
boolean[] isExisted = new boolean[s.length()];
// 记录每个字符出现的次数(求最小字典序时会用到)
Map<Character, Integer> cntMap = new HashMap<>();
// 初始化字符出现次数
for (char ch : s.toCharArray()) {
if (!cntMap.containsKey(ch))
cntMap.put(ch, 1);
else
cntMap.put(ch, cntMap.get(ch) + 1);
}
// 更新栈
for (char ch : s.toCharArray()) {
// 每遍历一个字符对应的计数减 1。(后续串中该元素还剩多少个)
cntMap.put(ch, cntMap.get(ch) - 1);
// 重复字符不处理
if (isExisted[ch - 'a'] == true) continue;
// 重点是处理入栈的小字符
while (!stack.isEmpty() && ch < stack.peek()) {
if (cntMap.get(stack.peek()) == 0)
break;
isExisted[stack.pop() - 'a'] = false;
}
// 常规入栈并标记动作
stack.push(ch);
isExisted[ch - 'a'] = true;
// System.out.println(isExisted.length + " : " + Arrays.toString(isExisted));
}
// 出栈字符与实际字符反向
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
return sb.reverse().toString();
}
171-Excel 表列序号
题意分析:
解题思路:
注意点:
代码:
public int titleToNumber(String columnTitle) {
int ret = 0;
for (int i = 0; i < columnTitle.length(); i++) {
char ch = columnTitle.charAt(i);
int val = ch - 'A' + 1;
ret += val * Math.pow(26, columnTitle.length() - i -1);
}
return ret;
}
415-字符串相加(大数相加)
题意分析:
- 两个大数以字符串的形式做加法。
解题思路:
- 按位计算,个位+个位,十位+十位……
- 时间复杂度:max(O(m), O(n))
注意点:
扩展:
- 多个大数相加如何处理,超过 INT 最大值如何处理,如果有负数又如何处理。
代码:
/**
* 解题思路:按位相加,如何处理 sum 和 carry
*
* 扩展:
* 1、如果处理多个字符串的相加
* 2、如果相加中包含负数该如何处理
* 3、相加后如何判断超过 Int 类型限制(大数相加)
* @param num1
* @param num2
* @return
*/
public String addTwoStrings(String num1, String num2) {
int i = num1.length() - 1, j = num2.length() - 1;
StringBuilder sb = new StringBuilder();
int carry = 0, sum = 0;
while (i >= 0 || j >= 0) {
// 双指针按位相加,如果没有位则按0值处理
int a = i >= 0 ? num1.charAt(i) - '0' : 0;
int b = j >= 0 ? num2.charAt(j) - '0' : 0;
// System.out.println("a = " + a + "; b = " + b);
sum = a + b + carry;
carry = sum / 10;
sb.append(sum % 10);
i--;
j--;
}
if (carry == 1) sb.append(carry);
return sb.reverse().toString();
}
/**
* 多个字符串相加
* @param strs
* @return
*/
public String addMultiStrings(String[] strs) {
if (strs.length == 1) return strs[0];
String twoSum = addTwoStrings(strs[0] , "0");
System.out.println("first = " + twoSum);
for (int i = 1; i < strs.length; i++) {
twoSum = addTwoStrings(strs[i], twoSum);
System.out.println("iter twoSum = " + twoSum);
}
return twoSum;
}
模版
题意分析:
解题思路:
注意点:
扩展:
代码: