【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(n^{2})

  1. 固定一个中心点
  2. 从中心点开始,向两边扩展(注意:奇数长度和偶数长度都需要考虑)

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)

高精度相乘

解法一:“模拟”列竖式运算(代码较长,不好写)

  1. 细节一:高位相乘的时候,要补上“0”
  2. 细节二:处理前导“0”
  3. 细节三:注意计算结果的顺序

解法二:

对解法一做优化(无进位相乘然后相加,最后处理进位)

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();
    }
}
比较含退格的字符串

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)

解法:利用栈来模拟计算过程

分情况讨论:

  1. 遇到操作符:更新op
  2. 遇到数字:先把数字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)

解法:用两个栈来模拟(细节:字符串这个栈中,先放入一个空串)

分情况讨论:

  1. 遇到数字:提取出这个数字,放入“数字栈”中
  2. 遇到‘ [ ’:把后面的字符串提取出来,放入“字符串栈”中
  3. 遇到‘ ] ’:对两个栈顶进行解析,然后接在“字符串栈”栈顶字符串之后
  4. 遇到单独字符:直接接在“字符串栈”栈顶字符串之后
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)

解法:借助“栈”模拟即可

  1. 让元素一直进栈
  2. 进栈的同时,判断是否出栈
  3. 所有元素进栈完毕之后,判断 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();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值