每日算法22_2020-11-28

来源:题库 - 力扣 (LeetCode) (leetcode-cn.com)。侵删。

题目一

493. 翻转对

给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。

你需要返回给定数组中的重要翻转对的数量。

示例 1:

输入: [1,3,2,3,1]
输出: 2
示例 2:

输入: [2,4,3,5,1]
输出: 3
注意:

给定数组的长度不会超过50000。
输入数组中的所有数字都在32位整数的表示范围内。

我的答案超时了!

啪一下很快嗷!然后超时!T T

只能想到暴力解法了。

public int reversePairs(int[] nums) {
    int n = 0;
    for(int i = 0; i < nums.length - 1; i++){
        for (int j = i + 1; j < nums.length; j++){
            if((long) nums[i] > 2 * (long) nums[j]) n++;
        }
    }

    return n;
}

官方答案:归并排序

没太明白。

思路及解法

在归并排序的过程中,假设对于数组nums[l…r] 而言,我们已经分别求出了子数组nums[l…m] 与 nums[m+1…r] 的翻转对数目,并已将两个子数组分别排好序,则nums[l…r] 中的翻转对数目,就等于两个子数组的翻转对数目之和,加上左右端点分别位于两个子数组的翻转对数目。

我们可以为两个数组分别维护指针i,j。对于任意给定的 i 而言,我们不断地向右移动 j,直到 nums[i]≤2⋅nums[j]。此时,意味着以 i 为左端点的翻转对数量为j−m−1。随后,我们再将 i 向右移动一个单位,并用相同的方式计算以 i 为左端点的翻转对数量。不断重复这样的过程,就能够求出所有左右端点分别位于两个子数组的翻转对数目。

class Solution {
    public int reversePairs(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        return reversePairsRecursive(nums, 0, nums.length - 1);
    }

    public int reversePairsRecursive(int[] nums, int left, int right) {
        if (left == right) {
            return 0;
        } else {
            int mid = (left + right) / 2;
            int n1 = reversePairsRecursive(nums, left, mid);
            int n2 = reversePairsRecursive(nums, mid + 1, right);
            int ret = n1 + n2;

            // 首先统计下标对的数量
            int i = left;
            int j = mid + 1;
            while (i <= mid) {
                while (j <= right && (long) nums[i] > 2 * (long) nums[j]) {
                    j++;
                }
                ret += j - mid - 1;
                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++] = nums[p2++];
                } else if (p2 > right) {
                    sorted[p++] = nums[p1++];
                } else {
                    if (nums[p1] < nums[p2]) {
                        sorted[p++] = nums[p1++];
                    } else {
                        sorted[p++] = nums[p2++];
                    }
                }
            }
            for (int k = 0; k < sorted.length; k++) {
                nums[left + k] = sorted[k];
            }
            return ret;
        }
    }
}

复杂度分析

时间复杂度:O(n^2)。我们使用了两次二重循环,时间复杂度均为 O(n^2)。在循环中对哈希映射进行的修改以及查询操作的期望时间复杂度均为 O(1),因此总时间复杂度为 O(n^2)。

空间复杂度:O(n^2),即为哈希映射需要使用的空间。在最坏的情况下,A[i]+B[j] 的值均不相同,因此值的个数为 n^2 ,也就需要 O(n^2)的空间。

方法二:树状数组

思路及解法

树状数组支持的基本查询为求出 [1,val]之间的整数数量。因此,对于 nums[i] 而言,我们首先查询 [1,2⋅nums[i]]的数量,再求出[1,maxValue]的数量(其中maxValue 为数组中最大元素的二倍)。二者相减,就能够得到以 i 为右端点的翻转对数量。

由于数组中整数的范围可能很大,故在使用树状数组解法之前,需要利用哈希表将所有可能出现的整数,映射到连续的整数区间内。

class Solution {
    public int reversePairs(int[] nums) {
        Set<Long> allNumbers = new TreeSet<Long>();
        for (int x : nums) {
            allNumbers.add((long) x);
            allNumbers.add((long) x * 2);
        }
        // 利用哈希表进行离散化
        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 < nums.length; i++) {
            int left = values.get((long) nums[i] * 2), right = values.size() - 1;
            ret += bit.query(right + 1) - bit.query(left + 1);
            bit.update(values.get((long) nums[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;
    }
}

复杂度分析

  • 时间复杂度:O*(Nlog*N),其中 N为数组的长度。
  • 空间复杂度:O*(*N),其中 N为数组的长度。

题目二

5557. 最大重复子字符串

给你一个字符串 sequence ,如果字符串 word 连续重复 k 次形成的字符串是 sequence 的一个子字符串,那么单词 word 的 重复值为 k 。单词 word 的 最大重复值 是单词 word 在 sequence 中最大的重复值。如果 word 不是 sequence 的子串,那么重复值 k 为 0 。

给你一个字符串 sequence 和 word ,请你返回 最大重复值 k 。

示例 1:

输入:sequence = “ababc”, word = “ab”
输出:2
解释:“abab” 是 “ababc” 的子字符串。
示例 2

输入:sequence = “ababc”, word = “ba”
输出:1
解释:“ba” 是 “ababc” 的子字符串,但 “baba” 不是 “ababc” 的子字符串。
示例 3:

输入:sequence = “ababc”, word = “ac”
输出:0
解释:“ac” 不是 “ababc” 的子字符串。

提示:

1 <= sequence.length <= 100
1 <= word.length <= 100
sequence 和 word 都只包含小写英文字母。

我的答案

class Solution {
    public int maxRepeating(String sequence, String word) {
        int n = 0;
        if (!sequence.contains(word)) return n;
        StringBuilder str = new StringBuilder(word);
        while(sequence.contains(str.toString())){
            n++;
            str.append(word);
        }
        return n;
    }
}

其实这题也可以用数组来做,速度能提高,空间也会减少。

题目三

5558. 合并两个链表

图像来自于leetcode该题描述,侵删。

给你两个链表 list1 和 list2 ,它们包含的元素分别为 n 个和 m 个。

请你将 list1 中第 a 个节点到第 b 个节点删除,并将list2 接在被删除节点的位置。

下图中蓝色边和节点展示了操作后的结果:

img

请你返回结果链表的头指针。

示例 1:

img

输入:list1 = [0,1,2,3,4,5], a = 3, b = 4, list2 = [1000000,1000001,1000002]
输出:[0,1,2,1000000,1000001,1000002,5]
解释:我们删除 list1 中第三和第四个节点,并将 list2 接在该位置。上图中蓝色的边和节点为答案链表。
示例 2:

img

输入:list1 = [0,1,2,3,4,5,6], a = 2, b = 5, list2 = [1000000,1000001,1000002,1000003,1000004]
输出:[0,1,1000000,1000001,1000002,1000003,1000004,6]
解释:上图中蓝色的边和节点为答案链表。

提示:

3 <= list1.length <= 104
1 <= a <= b < list1.length - 1
1 <= list2.length <= 104

我的答案

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode p = list1; // 指针p指向 list1 的头节点
        ListNode q = list2; // 指针q指向 list2 的头节点
        int i = 0; // list1计数
        int j = 0; // list2计数
        ListNode x = null;
        ListNode y = null;
        while(p != null){
            if(i+1 == a){
                // 当i到a了
                x = p; // 记录当前起始位置
            }
            if(i == b){
                y = p.next;
                break;
            }
            i++;
            p = p.next;
        }
        while(q.next != null){ // 到最后一个节点结束
            q = q.next;
        }
        
        // 连接链表
        x.next = list2;
        q.next = y;
        
        return list1;
    }
}

我写的太臃肿了,好多都没必要的。

1ms的答案

class Solution {
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode node = list1;
        for (int i = 0; i < a - 1; i++) {
            node = node.next;
        }
        ListNode node1 = node.next;
        for (int i = 0; i < (b - a + 1); i++) {
            node1 = node1.next;
        }
        node.next = list2;
        while (node.next != null){
            node = node.next;
        }
        node.next = node1;
        return list1;
    }
}

< a - 1; i++) {
node = node.next;
}
ListNode node1 = node.next;
for (int i = 0; i < (b - a + 1); i++) {
node1 = node1.next;
}
node.next = list2;
while (node.next != null){
node = node.next;
}
node.next = node1;
return list1;
}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值