【每日一题】2020.11月每日刷题总结


本专栏主要的针对每日一题的更新,会总结一些比较好的每日一题。

140.单词拆分II H(dfs,dp)

在这里插入图片描述

class Solution {
    public List<String> wordBreak(String s, List<String> wordDict) {
        // 在整体的思路上,需要首先维护一个dp[i]表示第i位之前的内容全部都是可以在字典中的。这里不包括第i位。
        int n = s.length();
        boolean[] dp = new boolean[n+1];
        dp[0] = true;
        HashSet<String> set = new HashSet<>(wordDict);
        // 这里为什么是这个循环的顺序呢?
        // 这里的枚举有点类似与区间dp的思路,我们是需要判断从第i位及其之前都可以
        for (int i = 1; i<=n;i++){
            for (int j = i-1;j>=0;j--){
                String cur = s.substring(j,i);
                if (set.contains(cur) && dp[j]){
                    dp[i] = true;  // dp[i]其实不包括i的,也就是i之前的
                    break; // 这里使用break其实可以加快速度 因为我们只需要得到一次dp[i] = true
                }
            }
        }
        List<String> ans = new LinkedList<>();
        // dp[n]表示了当前字符串是可以完成整个都在字典中的。因此在进行dfs
        if (dp[n]){
            LinkedList<String> cur = new LinkedList<>();
            dfs(dp, s, set, n, cur, ans);
        }
        return ans;
    }

    private void dfs(boolean[] dp, String s, HashSet<String> wordDict, int right, LinkedList<String> cur, List<String> ans){
        if (right == 0){
            String mayans = String.join(" ", cur);
            ans.add(mayans);
            return;
        }
        for (int i = right-1;i>=0;i--){
            String now = s.substring(i,right);
            if (wordDict.contains(now) && dp[i]){
                cur.addFirst(now);
                dfs(dp, s, wordDict, i, cur, ans);
                cur.removeFirst();
            }
        }

    }
}

57.插入区间H(判断区间重叠的标准)

在这里插入图片描述

class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        // 思路:枚举原始的区间,依次加入新的列表中。如果发生交集
        int left = newInterval[0];
        int right = newInterval[1];
        boolean placed = false; // 标识是否已经插入
        List<int[]> ansList = new ArrayList<int[]>();
        // 判断有没有交集,只需要判断是不是右端点小于左端点或者左端点大于右端点即可。
        for (int[] interval : intervals) {
            if (interval[0] > right) {
                // 在插入区间的右侧且无交集
                if (!placed) {
                    // 因为已经在前一个的左侧,当前的右侧,因此可以直接加入。
                    ansList.add(new int[]{left, right});
                    placed = true;                    
                }
                ansList.add(interval);
            } else if (interval[1] < left) {
                // 在插入区间的左侧且无交集
                ansList.add(interval);
            } else {
                // 与插入区间有交集,计算它们的并集
                left = Math.min(left, interval[0]);
                right = Math.max(right, interval[1]);
                // 这一步实际上是合成了新的区间。
            }
        }
        if (!placed) {
            ansList.add(new int[]{left, right});
        }
        
        // 因为不好确定具体的长度。首先借助了List,最后再转换
        int[][] ans = new int[ansList.size()][2];
        for (int i = 0; i < ansList.size(); ++i) {
            ans[i] = ansList.get(i);
        }
        return ans;
    }
}

31.下一个排列M(思路)

在这里插入图片描述

class Solution {
    // 从后往前首先找到第一个不是升序的数字
    // 然后寻找该数字之后,寻找其后方,第一个大于它的位置。
    // 交换两者的位置,并对这一段数组进行排序
    public void nextPermutation(int[] nums) {
        if (nums == null || nums.length == 0) return;
        int firstIndex = -1;
        for (int i = nums.length - 2; i >= 0; i--) {
            if (nums[i] < nums[i + 1]) {
                firstIndex = i;
                break;
            }
        }
        if (firstIndex == -1) {
            reverse(nums, 0, nums.length - 1);
            return;
        }
        // 步骤二
        int secondIndex = -1;
        for (int i = nums.length - 1; i >= 0; i--) {
            if (nums[i] > nums[firstIndex]) {
                secondIndex = i;
                break;
            }
        }
        swap(nums, firstIndex, secondIndex);
        reverse(nums, firstIndex + 1, nums.length - 1);
        return;

    }

    private void reverse(int[] nums, int i, int j) {
        while (i < j) {
            swap(nums, i++, j--);
        }
    }

    private void swap(int[] nums, int i, int i1) {
        int tmp = nums[i];
        nums[i] = nums[i1];
        nums[i1] = tmp;
    }
}

328.奇偶链表M(java中链表是地址信息)

在这里插入图片描述

对于链表问题,还是需要想清楚每个listnode保存是地址还是什么。

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head == null)return head;
        ListNode evenhead = head.next;// 这里保存的是一个地址。
        ListNode even = head.next, odd = head;
        while (even!=null && even.next!=null){
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = evenhead;
        return head;
    }
}

514.自由之路H(dp)

在这里插入图片描述

class Solution {
    public int findRotateSteps(String ring, String key) {
        // dp[i][j]key第i位与ring第j位
        int n = ring.length();
        int m = key.length();
        List<Integer>[] map = new List[26]; // 这里最好注明类型,否则会默认位object类型
        for (int i = 0; i<26;i++){
            map[i] = new ArrayList<>();
        }
        for (int i = 0; i<n; i++){
            int index = ring.charAt(i)-'a';
            map[index].add(i);
        }
        // map 保存了每个字符对应的出现在轮盘中的位置。
        int[][] dp = new int[m][n];
        for (int i = 0; i<m;i++){
            Arrays.fill(dp[i], 1000000);
        }
        for (int i: map[key.charAt(0)-'a']) dp[0][i] = Math.min(i, n-i)+1; // 确认第一个字母需要的操作
        // 依次枚举,字符串中的字母,当前字符在轮盘中的位置,之前轮盘在的位置
        for (int i = 1; i<m;i++){
            int index = key.charAt(i)-'a';
            for (int j:map[index]){// 查找这个数字在轮盘上的位置
                int index_pre = key.charAt(i-1)-'a'; // 前一个字母的位置,因为需要从前一个字母的位置转移过来
                for (int k :map[index_pre]){
                    dp[i][j] = Math.min(dp[i][j], dp[i-1][k]+Math.min(Math.abs(j-k), n-Math.abs(j-k))+1);
                }
            }
        }
        int ans = 100000000;
        for (int i = 0;i<n;i++){
            ans = Math.min(ans, dp[m-1][i]);
        }
        return ans;
    }
}

402.移除k位数字M(单调栈)

在这里插入图片描述
在这里插入图片描述

class Solution {
    // 如果这个数字比之后的位数大,删除
    // 第一位需要考虑后一个数不能是0,这样可能复杂度会有点高,因为最差情况会不断的进行循环
    // 采用单调栈升级,并且在最后注意维护的一下开头全是0的情况。
    public String removeKdigits(String num, int k) {
        Deque<Character> pq = new LinkedList<>();
        int n = num.length();
        int i = 0;
        for(i =0;i<n;i++){
            Character cur = num.charAt(i);
            while (pq.size()>0 && k>0 &&  pq.peekLast()>cur){
                pq.pollLast();
                k--;
            }
            pq.offerLast(cur);
        }
        while(k>0){
            pq.pollLast();
            k--;
        }
        StringBuffer ans = new StringBuffer();
        // 处理0开头的情况
        boolean begin = true;
        while (pq.size()>0){
            if (begin && pq.peekFirst() == '0'){
                pq.pollFirst();
                continue;
            }
            begin = false;
            ans.append(pq.pollFirst());
        }
        return ans.length() == 0? "0" : ans.toString();
    }
}

406.根据身高重建队列M(思路)

在这里插入图片描述

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        // 总体思路,首先排序,然后从前往后排位置。这样的方法可以保证不发生冲突

        // 首先排序,先根据身高小的在前,相同情况下,序列大的在前
        Arrays.sort(people, new Comparator<int[]>() {
            public int compare(int[] person1, int[] person2) {
                if (person1[0] != person2[0]) {
                    return person1[0] - person2[0];
                } else {
                    return person2[1] - person1[1];
                }
            }
        });

        int n = people.length;
        int[][] ans = new int[n][];
        for (int[] person : people) {
            int spaces = person[1] + 1;// 当前元素应该处于的位置
            for (int i = 0; i < n; ++i) {
                if (ans[i] == null) {
                    --spaces; // 前面的空位置
                    if (spaces == 0) {// 找到了合适的位置
                        ans[i] = person;
                        break;
                    }
                }
            }
        }
        return ans;
    }
}

439.反转对H(归并思想应用)

在这里插入图片描述

class Solution {
    // 思路:利用归并排血解决。
    int reverseans = 0;
    public int reversePairs(int[] nums) {
        int[] ans = merge(nums);
        //System.out.println(Arrays.toString(ans));
        return reverseans;
    }
    // mergesorts函数,返回排序好的数组
    public int[] mergesorts(int[] nums){
        int n = nums.length;
        if (n == 0 || n == 1) return nums;
        int[] left = mergesorts(Arrays.copyOfRange(nums, 0, n/2));// 这里需要复制数组
        int[] right = merge(Arrays.copyOfRange(nums, n/2, n));
        return help(left, right);
    }
    // 首先在这里计算反转对,然后再进行合并
    public int[] help(int[] l, int[] r){
        int n = l.length;
        int m = r.length;
        int[] ans = new int[n+m];
        int x = 0;
        int y = 0;
        while (x<n){
            while (y<m && (long)l[x]>(long)2*r[y]){
                y++;
            }
            reverseans += y; // 需要加上后数组已经排除的。肯定都小于当前位置的数值。
            x++;
        }
        // 在进行合并
        x = 0;
        y = 0;
        while (x<n && y<m){
            if (l[x]>=r[y]){
                ans[x+y] = r[y];
                y++;
            }else{
                ans[x+y] = l[x];
                x++;
            }
        } 
        while (x<n){
            ans[x+y] = l[x];
            x++;
        }
        while (y<m){
            ans[x+y] = r[y];
            y++;
        }
        return ans;
    }
}

327.区间的个数H(前缀和,归并排序的应用,线段树,树状数组,利用哈希数组离散化)

在这里插入图片描述

class Solution {
    public int countRangeSum(int[] nums, int lower, int upper) {
        long s = 0;
        long[] sum = new long[nums.length + 1];
        for (int i = 0; i < nums.length; ++i) {
            s += nums[i];
            sum[i + 1] = s;
        }
        return countRangeSumRecursive(sum, lower, upper, 0, sum.length - 1);
    }

    public int countRangeSumRecursive(long[] sum, int lower, int upper, int left, int right) {
        if (left == right) {
            return 0;
        } else {
            int mid = (left + right) / 2;
            int n1 = countRangeSumRecursive(sum, lower, upper, left, mid);
            int n2 = countRangeSumRecursive(sum, lower, upper, mid + 1, right);
            // 首先是左右两个各自的下标对的数量
            int ret = n1 + n2;

            // 然后统计跨越mid对的数量
            int i = left;
            int l = mid + 1;
            int r = mid + 1;
            while (i <= mid) {
                while (l <= right && sum[l] - sum[i] < lower) {
                    l++;
                }
                while (r <= right && sum[r] - sum[i] <= upper) {
                    r++;
                }
                // 起始点是i,终点在ll,rr之间的。
                ret += r - l;
                i++;
            }

            // 随后合并两个排序数组
            int[] sorted = new int[right - left + 1];
            int p1 = left, p2 = mid + 1;
            int p = 0;
            while (p1 <= mid || p2 <= right) {
                if (p1 > mid) {
                    sorted[p++] = (int) sum[p2++];
                } else if (p2 > right) {
                    sorted[p++] = (int) sum[p1++];
                } else {
                    if (sum[p1] < sum[p2]) {
                        sorted[p++] = (int) sum[p1++];
                    } else {
                        sorted[p++] = (int) sum[p2++];
                    }
                }
            }
            for (int j = 0; j < sorted.length; j++) {
                sum[left + j] = sorted[j];
            }
            return ret;
        }
    }
}

=========================个人比较习惯的版本========================================
class Solution {
    int lower;
    int upper;
    public int countRangeSum(int[] nums, int lower, int upper) {
            // 归并排序的应用
            this.lower = lower;
            this.upper = upper;
            int n = nums.length;
            long[] sum = new long[n+1];
            for(int i = 1; i<=n;i++){
                sum[i] = sum[i-1]+nums[i-1];
            }
            return count(sum);
        }

        private long[] mergesort(long[] sum){
            int n = sum.length;
            if (n == 0 || n == 1)return sum;
            int mid = n/2;
            long[] left = mergesort(Arrays.copyOfRange(sum, 0, mid));
            long[] right = mergesort(Arrays.copyOfRange(sum, mid, n));
            return merge(left, right);
        }

        private long[] merge(long[] left, long[] right){
            int n = left.length;
            int m = right.length;
            int l = 0, r = 0;
            long[] ans = new long[n+m];
            while(l<n && r<m){
                if (left[l]<right[r]){
                    ans[l+r] = left[l];
                    l++;
                }else{
                    ans[l+r] = right[r];
                    r++;
                }
            }
            while (l<n){
                ans[l+r] = left[l];
                l++;
            }
            while(r<m){
                ans[l+r] = right[r];
                r++;
            }
            return ans;
        }

        private int count(long[] nums){
            int N = nums.length;
            if (N == 0 || N == 1)return 0;
            int mid = N/2;
            // 先统计两侧的,再分别对两侧进行排序,然后再统计跨越的。
            long[] raw_left = Arrays.copyOfRange(nums, 0, mid);
            long[] raw_right = Arrays.copyOfRange(nums, mid, N);
            int ret = count(raw_left)+count(raw_right);

            long[] left = mergesort(raw_left);
            long[] right = mergesort(raw_right);

            int n = left.length;
            int m = right.length;
            int l = 0, r = 0;
            int i = 0;
            while(i<n){
                while (l<m && right[l]-left[i]<lower){
                    l++;
                }
                while(r<m && right[r]-left[i]<=upper){
                    r++;
                }
                ret += r-l;
                i++;
            }
            return ret;
        }
}

对于区间的查询还可以借助线段树和树状数组实现。注意到整数的范围可能很大,故需要利用哈希表将所有可能出现的整数,映射到连续的整数区间内。

==============================这里有一个简单版本的线段树==============================
class Solution {
    public int countRangeSum(int[] nums, int lower, int upper) {
        long sum = 0;
        long[] preSum = new long[nums.length + 1];
        for (int i = 0; i < nums.length; ++i) {
            sum += nums[i];
            preSum[i + 1] = sum;
        }
        // 所有可能用到的整数
        Set<Long> allNumbers = new TreeSet<Long>();
        for (long x : preSum) {
            allNumbers.add(x);
            allNumbers.add(x - lower);
            allNumbers.add(x - upper);
        }
        // 利用哈希表进行离散化
        Map<Long, Integer> values = new HashMap<Long, Integer>();
        int idx = 0;
        for (long x : allNumbers) {
            values.put(x, idx);
            idx++;
        }

        SegNode root = build(0, values.size() - 1);
        int ret = 0;
        for (long x : preSum) {
            int left = values.get(x - upper), right = values.get(x - lower);
            ret += count(root, left, right);
            insert(root, values.get(x));
        }
        return ret;
    }

    public SegNode build(int left, int right) {
        SegNode node = new SegNode(left, right);
        if (left == right) {
            return node;
        }
        int mid = (left + right) / 2;
        node.lchild = build(left, mid);
        node.rchild = build(mid + 1, right);
        return node;
    }

    public int count(SegNode root, int left, int right) {
        if (left > root.hi || right < root.lo) {
            return 0;
        }
        if (left <= root.lo && root.hi <= right) {
            return root.add;
        }
        return count(root.lchild, left, right) + count(root.rchild, left, right);
    }

    public void insert(SegNode root, int val) {
        root.add++;
        if (root.lo == root.hi) {
            return;
        }
        int mid = (root.lo + root.hi) / 2;
        if (val <= mid) {
            insert(root.lchild, val);
        } else {
            insert(root.rchild, val);
        }
    }
}

class SegNode {
    int lo, hi, add;
    SegNode lchild, rchild;

    public SegNode(int left, int right) {
        lo = left;
        hi = right;
        add = 0;
        lchild = null;
        rchild = null;
    }
}

==================树状数组=============================
class Solution {
    public int countRangeSum(int[] nums, int lower, int upper) {
        long sum = 0;
        long[] preSum = new long[nums.length + 1];
        for (int i = 0; i < nums.length; ++i) {
            sum += nums[i];
            preSum[i + 1] = sum;
        }
        
        Set<Long> allNumbers = new TreeSet<Long>();
        for (long x : preSum) {
            allNumbers.add(x);
            allNumbers.add(x - lower);
            allNumbers.add(x - upper);
        }
        // 利用哈希表进行离散化
        Map<Long, Integer> values = new HashMap<Long, Integer>();
        int idx = 0;
        for (long x: allNumbers) {
            values.put(x, idx);
            idx++;
        }

        int ret = 0;
        BIT bit = new BIT(values.size());
        for (int i = 0; i < preSum.length; i++) {
            int left = values.get(preSum[i] - upper), right = values.get(preSum[i] - lower);
            ret += bit.query(right + 1) - bit.query(left);
            bit.update(values.get(preSum[i]) + 1, 1);
        }
        return ret;
    }
}

class BIT {
    int[] tree;
    int n;

    public BIT(int n) {
        this.n = n;
        this.tree = new int[n + 1];
    }

    public static int lowbit(int x) {
        return x & (-x);
    }

    public void update(int x, int d) {
        while (x <= n) {
            tree[x] += d;
            x += lowbit(x);
        }
    }

    public int query(int x) {
        int ans = 0;
        while (x != 0) {
            ans += tree[x];
            x -= lowbit(x);
        }
        return ans;
    }
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值