目录
分治-快排算法
分治:分而治之
分治-快排
颜色分类
75. 颜色分类 - 力扣(LeetCode)(写该题之前先写283.移动零)
移动零
算法思路:
排序数组
利用数组划分实现快速排序
"数组分三块"(核心)
优化:用随机的方式选择基准元素
数组中的第K个最大元素
215. 数组中的第K个最大元素 - 力扣(LeetCode)
堆排序O(NlogN),快速排序算法O(N)
算法原理:数组分三块+随机选择基准元素
分情况讨论
最小K个数
面试题 17.14. 最小K个数 - 力扣(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中当前元素的原始下标是多少?
左右部分的下标已被重新排序,下标已经不是原来的下标了
需要用两个辅助数组
翻转对(困难)
解法:分治
计算翻转对:
策略一:计算当前元素后面,有多少元素的两倍比我小(降序)
策略二:计算当前元素之前,有多少元素的一半比我大(升序)
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;
}
}
字符串
最长公共前缀
解法一:两两比较
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];// 都一样
}
}
最长回文子串
解法:中心扩展算法 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);
}
}
二进制求和
高精度加法(模拟列竖式运算)
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();
}
}
字符串相乘
高精度相乘
解法一:“模拟”列竖式运算(代码较长,不好写)
- 细节一:高位相乘的时候,要补上“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. 处理前导0
while (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();
}
}
比较含退格的字符串
解法:利用栈模拟即可
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
解法:利用栈来模拟计算过程
分情况讨论:
- 遇到操作符:更新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;
}
}
字符串解码
解法:用两个栈来模拟(细节:字符串这个栈中,先放入一个空串)
分情况讨论:
- 遇到数字:提取出这个数字,放入“数字栈”中
- 遇到‘ [ ’:把后面的字符串提取出来,放入“字符串栈”中
- 遇到‘ ] ’:对两个栈顶进行解析,然后接在“字符串栈”栈顶字符串之后
- 遇到单独字符:直接接在“字符串栈”栈顶字符串之后
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();
}
}
验证栈序列
解法:借助“栈”模拟即可
- 让元素一直进栈
- 进栈的同时,判断是否出栈
- 所有元素进栈完毕之后,判断 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();
}
}