【leetcode题解】算法练习
目录
分治-快排算法
颜色分类
移动零
排序数组
数组中的第K个最大元素
最小K个数
分治-归并排序
排序数组
交易逆序对的总数(困难)
计算右侧小于当前元素的个数(困难)
翻转对(困难)
字符串
最长公共前缀
最长回文子串
二进制求和
字符串相乘
栈
删除字符串中的所有相邻重复项
比较含退格的字符串
基本计算器 II
字符串解码
验证栈序列
分治-快排算法
分治:分而治之
分治-快排
颜色分类
75. 颜色分类 - 力扣(LeetCode)(写该题之前先写283.移动零)
移动零
283. 移动零 - 力扣(LeetCode)(快排)
算法思路:
排序数组
912. 排序数组 - 力扣(LeetCode)
利用数组划分实现快速排序
"数组分三块"(核心)
优化:用随机的方式选择基准元素
数组中的第K个最大元素
215. 数组中的第K个最大元素 - 力扣(LeetCode)
堆排序O(NlogN),快速排序算法O(N)
算法原理:数组分三块+随机选择基准元素
分情况讨论
最小K个数
面试题 17.14. 最小K个数 - 力扣(LeetCode)
方法:排序;堆;快速选择算法
数组分三块+随机选择基准元素
分治-归并排序
排序数组
912. 排序数组 - 力扣(LeetCode)
class Solution {public int[] sortArray(int[] nums) {mergeSort(nums, 0, nums.length - 1);return nums;}public void mergeSort(int[] nums, int left, int right) {if (left >= right)// 处理边界情况return;// 1. 根据中间点划分区间int mid = (left + right) / 2;// [left,mid] [mid+1,right]// 2. 先把左右区间排序mergeSort(nums, left, mid);mergeSort(nums, mid + 1, right);// 3. 合并两个有序数组// 定义两个指针,分别指向左右两个区间// 哪个小,就放在新的数组里面int[] ret = new int[right - left + 1];int cur1 = left, cur2 = mid + 1, i = 0;while (cur1 <= mid && cur2 <= right) {ret[i++] = nums[cur1] < nums[cur2] ? nums[cur1++] : nums[cur2++];}// 处理没有处理完的数组while (cur1 <= mid)ret[i++] = nums[cur1++];while (cur2 <= right)ret[i++] = nums[cur2++];// 4. 还原for (int j = left; j <= right; j++) {nums[j] = ret[j - left];// ret从0开始}}
}
交易逆序对的总数(困难)
LCR 170. 交易逆序对的总数 - 力扣(LeetCode)
解法一:暴力枚举(两层for循环)(可能会超时)
解法二:
1. 左半部分的个数+右半部分的个数+一左一右符合的个数
2. 左半部分->左排序->右半部分->右排序->一左一右+排序
3. 利用归并排序解决该问题:O(n·logn)
策略一:找出该数之前,有多少个数比我大
必须是升序,如果是降序,会有元素被重复统计
策略二:找出该数之后,有多少个数比我小
必须是降序,如果是升序,会有元素被重复统计
class Solution {int[] tmp;public int reversePairs(int[] record) {int n = record.length;tmp = new int[n];return mergeSort(record, 0, n - 1);}public int mergeSort(int[] nums, int left, int right) {if (left >= right)return 0;int ret = 0;// 1. 选择一个中间点,将数组划分成两部分int mid = (left + right) / 2;// [left,mid] [mid+1,right]// 2. 左半部分的个数+排序+右半部分的个数+排序ret += mergeSort(nums, left, mid);ret += mergeSort(nums, mid + 1, right);// 3. 一左一右的个数int cur1 = left, cur2 = mid + 1, i = 0;while (cur1 <= mid && cur2 <= right) {if (nums[cur1] > nums[cur2]) {ret += mid - cur1 + 1;tmp[i++] = nums[cur2++];} else {tmp[i++] = nums[cur1++];}}// 4. 处理一下排序while (cur1 <= mid)tmp[i++] = nums[cur1++];while (cur2 <= right)tmp[i++] = nums[cur2++];for (int j = left; j <= right; j++) {nums[j] = tmp[j - left];}return ret;}
}
计算右侧小于当前元素的个数(困难)
315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)
解法一:暴力枚举(超时)
解法二:归并排序(分治)
策略:当前元素的后面,有多少个比较小(降序)
问题:找到nums中当前元素的原始下标是多少?
左右部分的下标已被重新排序,下标已经不是原来的下标了
需要用两个辅助数组
翻转对(困难)
493. 翻转对 - 力扣(LeetCode)
解法:分治
计算翻转对:
策略一:计算当前元素后面,有多少元素的两倍比我小(降序)
策略二:计算当前元素之前,有多少元素的一半比我大(升序)
class Solution {int[] tmp;public int reversePairs(int[] nums) {int n = nums.length;tmp = new int[n];return mergeSort(nums, 0, n - 1);}public int mergeSort(int[] nums, int left, int right) {if (left >= right)return 0;int ret = 0;// 1. 根据中间元素,将区间分为两部分int mid = (left + right) / 2;// [left,mid] [mid+1,right]// 2. 求出左右两个区间的翻转对ret += mergeSort(nums, left, mid);ret += mergeSort(nums, mid + 1, right);// 3. 处理一左一右 - 先计算翻转对int cur1 = left, cur2 = mid + 1, i = left;// 降序版本while (cur1 <= mid) {while (cur2 <= right && nums[cur2] >= nums[cur1] / 2.0)cur2++;if (cur2 > right)break;ret += right - cur2 + 1;cur1++;}// 4. 合并两个有序数组cur1 = left;cur2 = mid + 1;while (cur1 <= mid && cur2 <= right) {tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++];}while (cur1 <= mid)tmp[i++] = nums[cur1++];while (cur2 <= right)tmp[i++] = nums[cur2++];for (int j = left; j <= right; j++)nums[j] = tmp[j];return ret;}
}
字符串
最长公共前缀
14. 最长公共前缀 - 力扣(LeetCode)
解法一:两两比较
class Solution {public String longestCommonPrefix(String[] strs) {// 解法一:两两比较String ret = strs[0];for (int i = 1; i < strs.length; i++) {ret = findCommon(strs[i], ret);}return ret;}public String findCommon(String s1, String s2) {int i = 0;while (i < Math.min(s1.length(), s2.length()) && s1.charAt(i) == s2.charAt(i))i++;return s1.substring(0, i);}
}
解法二:统一比较
class Solution {public String longestCommonPrefix(String[] strs) {// 解法二:统一比较for(int i=0;i<strs[0].length();i++){for(int j=1;j<strs.length;j++){if(i==strs[j].length()||strs[j].charAt(i)!=strs[0].charAt(i)){return strs[0].substring(0,i);}}}return strs[0];// 都一样}
}
最长回文子串
5. 最长回文子串 - 力扣(LeetCode)
解法:中心扩展算法 O(
)
- 固定一个中心点
- 从中心点开始,向两边扩展(注意:奇数长度和偶数长度都需要考虑)
class Solution {public String longestPalindrome(String s) {int begin = 0, left = 0, right = 0, len = 0;for (int i = 0; i < s.length(); i++) {// 固定所有中间点// 扩展奇数长度的子串left = i;right = i;while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {left--;right++;}if (right - left - 1 > len) {begin = left + 1;len = right - left - 1;}// 扩展偶数长度的子串left = i;right = i + 1;while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {left--;right++;}if (right - left - 1 > len) {begin = left + 1;len = right - left - 1;}}return s.substring(begin, begin + len);}
}
二进制求和
67. 二进制求和 - 力扣(LeetCode)
高精度加法(模拟列竖式运算)
class Solution {public String addBinary(String a, String b) {StringBuffer ret = new StringBuffer();int cur1 = a.length() - 1, cur2 = b.length() - 1, t = 0;while (cur1 >= 0 || cur2 >= 0 || t != 0) {if (cur1 >= 0)t += a.charAt(cur1--) - '0';if (cur2 >= 0)t += b.charAt(cur2--) - '0';ret.append((char) ((t % 2) + '0'));t /= 2;}ret.reverse();return ret.toString();}
}
字符串相乘
43. 字符串相乘 - 力扣(LeetCode)
高精度相乘
解法一:“模拟”列竖式运算(代码较长,不好写)
- 细节一:高位相乘的时候,要补上“0”
- 细节二:处理前导“0”
- 细节三:注意计算结果的顺序
解法二:
对解法一做优化(无进位相乘然后相加,最后处理进位)
class Solution {public String multiply(String num1, String num2) {// 解法二:无进位相乘然后相加,最后处理进位// 1. 准备工作int m = num1.length(), n = num2.length();char[] n1 = new StringBuffer(num1).reverse().toString().toCharArray();//char[] n2 = new StringBuffer(num2).reverse().toString().toCharArray();int[] tmp = new int[m + n - 1];// 2. 无进位相乘,然后相加for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {tmp[i + j] += (n1[i] - '0') * (n2[j] - '0');}}// 3. 处理进位int cur = 0, t = 0;StringBuffer ret = new StringBuffer();while (cur < m + n - 1 || t != 0) {if (cur < m + n - 1)t += tmp[cur++];ret.append((char) ((t % 10) + '0'));//t /= 10;}// 4. 处理前导0while (ret.length() > 1 && ret.charAt(ret.length() - 1) == '0') {ret.deleteCharAt(ret.length() - 1);}return ret.reverse().toString();//}
}
栈
删除字符串中的所有相邻重复项
1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)
解法:利用栈进行模拟(直接用数组就可以模拟栈结构)
好处:模拟完毕之后,数组里面存的就是最终结果
class Solution {public String removeDuplicates(String ss) {StringBuffer ret = new StringBuffer();// 用数组来模拟栈结构char[] s = ss.toCharArray();for (char ch : s) {if (ret.length() > 0 && ch == ret.charAt(ret.length() - 1)) {// 出栈ret.deleteCharAt(ret.length() - 1);} else {ret.append(ch);}}return ret.toString();}
}
比较含退格的字符串
844. 比较含退格的字符串 - 力扣(LeetCode)
解法:利用栈模拟即可
class Solution {public boolean backspaceCompare(String s, String t) {return change(s).equals(change(t));}public String change(String s) {StringBuffer ret = new StringBuffer();// 用数组模拟栈结构char[] ss = s.toCharArray();for (char ch : ss) {if (ch == '#') {// 出栈(有字符的时候)if (ret.length() > 0)ret.deleteCharAt(ret.length() - 1);} else {// 入栈ret.append(ch);}}return ret.toString();}
}
基本计算器 II
227. 基本计算器 II - 力扣(LeetCode)
解法:利用栈来模拟计算过程
分情况讨论:
- 遇到操作符:更新op
- 遇到数字:先把数字tmp提取出来,分情况讨论,根据op的符号(op=='+',tmp直接入栈;op=='-',-tmp直接入栈;op=='*',直接乘栈顶元素;op=='/',直接除以栈顶元素)
class Solution {public int calculate(String ss) {Deque<Integer> st = new ArrayDeque<>();char op = '+';// 用来代表另一个字符栈int i = 0, n = ss.length();char[] s = ss.toCharArray();// 将字符转化为字符数组while (i < n) {if (s[i] == ' ')i++;else if (s[i] >= '0' && s[i] <= '9') {int tmp = 0;while (i < n && s[i] >= '0' && s[i] <= '9') {//tmp = (s[i++] - '0') + tmp * 10;}if (op == '+')st.push(tmp);if (op == '-')st.push(-tmp);if (op == '*')st.push(st.pop() * tmp);if (op == '/')st.push(st.pop() / tmp);} else {op = s[i++];}}// 统计结果int ret = 0;while (!st.isEmpty()) {ret += st.pop();}return ret;}
}
字符串解码
394. 字符串解码 - 力扣(LeetCode)
解法:用两个栈来模拟(细节:字符串这个栈中,先放入一个空串)
分情况讨论:
- 遇到数字:提取出这个数字,放入“数字栈”中
- 遇到‘ [ ’:把后面的字符串提取出来,放入“字符串栈”中
- 遇到‘ ] ’:对两个栈顶进行解析,然后接在“字符串栈”栈顶字符串之后
- 遇到单独字符:直接接在“字符串栈”栈顶字符串之后
class Solution {public String decodeString(String ss) {Stack<StringBuffer> st = new Stack<>();// 字符串栈st.push(new StringBuffer());// 先放进去一个空串 //Stack<Integer> nums = new Stack<>();// 数字栈int i = 0, n = ss.length();char[] s = ss.toCharArray();// 将字符串转化为字符数组while (i < n) {if (s[i] >= '0' && s[i] <= '9') {// 遇到数字int tmp = 0;while (i < n && s[i] >= '0' && s[i] <= '9') {tmp = tmp * 10 + (s[i] - '0');i++;}nums.push(tmp);} else if (s[i] == '[') {// 遇到'['i++;// 提取后面字符StringBuffer tmp = new StringBuffer();// 用来存放后面字符while (i < n && s[i] >= 'a' && s[i] <= 'z') {tmp.append(s[i]);// '[ ]'之间的字符串i++;}st.push(tmp);} else if (s[i] == ']') {int m = nums.pop();StringBuffer tmp = st.pop();while (m-- != 0) {st.peek().append(tmp);}i++;// 遍历']'下一个字符} else {// 遇到单独字符,接在“字符串栈”栈顶字符串之后StringBuffer tmp = new StringBuffer();// 用来存放后面字符while (i < n && s[i] >= 'a' && s[i] <= 'z') {tmp.append(s[i]);// '[ ]'之间的字符串i++;}st.peek().append(tmp);// 将tmp接在栈顶字符串之后}}return st.pop().toString();}
}
验证栈序列
946. 验证栈序列 - 力扣(LeetCode)
解法:借助“栈”模拟即可
- 让元素一直进栈
- 进栈的同时,判断是否出栈
- 所有元素进栈完毕之后,判断 i 是否已经遍历完毕或者判断栈是否为空即可
class Solution {public boolean validateStackSequences(int[] pushed, int[] popped) {Stack<Integer> nums = new Stack<>();int i = 0, n = popped.length;for (int x : pushed) {nums.push(x);while (!nums.isEmpty() && nums.peek() == popped[i]) {//nums.pop();i++;}}return nums.isEmpty();}
}