【Leetcode】数组问题整理笔记



283. Move Zeroes - 将零值移动到末尾

n/a

Solution:

class Solution:
    def get_zero_count(self, nums):
        ret = 0
        for i in nums:
            if i == 0:
                ret += 1
        return ret
    
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        count = self.get_zero_count(nums)
        i_head = 0
        i_tail = 0
        for _ in range(len(nums) - count):
            while nums[i_tail] == 0:
                i_tail += 1
            nums[i_head] = nums[i_tail]
            i_head += 1
            i_tail += 1

        for i in range(len(nums)-count, len(nums)):
            nums[i] = 0

n/a



Contains Duplicate – 是否存在重复值

217. Contains Duplicate Ⅰ

n/a

Solution:

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        s = set()
        for i in nums:
            if i not in s:
                s.add(i)
            else:
                return True
        return False

最后的提交结果挺令我意外的,原本以为我的空间消耗应该是比较大的,时间上应该算是比较少的,但是看来哪里有点儿问题。

我一时能想到的也就是使用 hash 做 O(1) 判断是否已经存在,消耗最坏 O(n) 的空间。
另一种是使用快排,消耗平均 O ( n l o g n ) O(nlogn) O(nlogn) 的时间,不占用额外的空间。

可能是 Leetcode 提供的 Solution 有更好的解法。

n/a


219. Contains Duplicate Ⅱ

n/a

Solution:

class Solution:
    def contains_duplicate(self, nums):
        return len(set(nums)) < len(nums)

    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        if k >= len(nums):
            return self.contains_duplicate(nums)

        _s = set()
        k = k+1  # for border
        for i in range(0, k):
            if nums[i] not in _s:
                _s.add(nums[i])
            else:
                return True
        
        for i in range(k, len(nums)):
            _s.remove(nums[i-k])
            if nums[i] in _s:
                return True
            else:
                _s.add(nums[i])
        return False

在理解题意之后不难写出合理的(使用 hash 方案)的解法。
不过需要注意的一点是,当 K ≥ length of nums K \geq \text{length of nums} Klength of nums 的时候,上面 Solution 的第 12 行(nums[i])会出错,因为 i 的值由 K 控制,越界。但是在这种情况下,这个问题会回归到了 217. Contains Duplicate Ⅰ,于是就可以针对这一个情况,做一下判断,然后调用 Ⅰ 中的解法。

这里的 contains_duplicate 完全可以用在 Ⅰ 中,算是一种写法简洁比较灵巧的做法。

多做这类算法题确实会对输入数据类型的不同做考虑,我在解决这道题时,就是自己手动测试了多种不同输入的情况,发现了 K > length of nums K > \text{length of nums} K>length of nums 这一特殊情况,才能做到 One-Pass

n/a

220. Contains Duplicate Ⅲ

n/a

Solution:

因为这一题 Leetcode 定义为中级的难度。在考虑的情况上确实相对复杂不少,所以这里的 Solution 分块介绍。

首先这个主要多了一个参数 t,所以原本 Ⅰ 和 Ⅱ 中的 solution 并不能直接用,而且考虑过后,也不仅仅是简单改改就能用的。

既然是多了一个参数 t,那么在这个参数不存在的时候,就和 Ⅱ 一致了; 即 t == 0 的时候。
所以可以有这个代码:

class Solution:
    def contains_nearby_duplicate(nums, k):
	    [...code from 219... ]

    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if t == 0:
            return self.contains_nearby_duplicate(nums, k)

但是在 K ≥ length of nums K \geq \text{length of nums} Klength of nums 的时候,和 Ⅰ 的情况很接近;
实际上,设想一下:如果在 Ⅰ 的问题上,加上 t 的条件(数组中是否存在两个数使得这两个数的大小相差不超过 t t t)产生的新问题,实际上不难解决 – 在排序之后的数组中找!

因此,在上面考虑 t==0 之前,不妨先考虑 K ≥ length of nums K \geq \text{length of nums} Klength of nums 的情况:

class Solution:
    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if len(nums) <= 1:
            return False

        if k >= len(nums):
            if t == 0:
                return len(set(nums)) < len(nums)
            if len(nums) == 2:
                return abs(nums[0] - nums[1]) <= t
            new_nums = sorted(nums)
            middle = len(new_nums) // 2
            return new_nums[middle + 1] - new_nums[middle] <= t or new_nums[middle] - new_nums[middle-1] <= t

我们可以将其放入到一个函数中,这样 containsNearbyAlmostDuplicate 函数就不会太长:

class Solution:
    def contains_almost_duplicate(self, nums, t):
        if t == 0:
            return len(set(nums)) < len(nums)
        if len(nums) == 2:
            return abs(nums[0] - nums[1]) <= t
        new_nums = sorted(nums)
        middle = len(new_nums) // 2
        return new_nums[middle + 1] - new_nums[middle] <= t or new_nums[middle] - new_nums[middle-1] <= t

    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if len(nums) <= 1:
            return False

        if k >= len(nums):
            return self.contains_almost_duplicate(nums, t)

考虑往 K 的特殊情况之后,我们在回过头来考虑 t,这时候在 t==0 的特殊情况时就回到了 Ⅱ 的问题:

class Solution:
    def contains_almost_duplicate(self, nums, t):
        [...]

    def contains_nearby_duplicate(nums, k):
	    [...code from 219... ]

    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if len(nums) <= 1:
            return False

        if k >= len(nums):
            return self.contains_almost_duplicate(nums, t)
        if t == 0:
            return self.contains_nearby_duplicate(nums, k)

剩下的就是 K ≤ length of nums K \leq \text{length of nums} Klength of nums AND t = 0̸ t =\not 0 t=0 的情况了。
在继续下面的解法之前,我先说一下原本剩下的我是和 Ⅰ,Ⅱ 一样使用 hash 做查找的思路去解决的,但是后来出现运行时间超时的情况( t t t 值很大的测试用例)。所以 hash 的方法不行,那么就回到我在 Ⅰ 中讲过,可以使用排序的方式。
而排序的解法,在 contains_almost_duplicate (Ⅰ + t t t 问题变体)中已经有所体现 —— 使用中位数和它前后一个数的差值与 t t t 比较。

在使用中位数的这种方法的时候,因为用到三个数,所以要提前考虑一下数组中没有三个数的情况。
所以,这里是 K ≤ length of nums K \leq \text{length of nums} Klength of nums length of dst-nums < 3 \text{length of dst-nums} < 3 length of dst-nums<3(即 K = 0 , 1 K = 0,1 K=0,1查找的数组长度为 1 , 2 1,2 1,2)的情况(当然,实际上考虑的就是 K = 1 K= 1 K=1,两个两个查找的情况)。

class Solution:
    [...省略代码...]

    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if len(nums) <= 1:
            return False

        if k >= len(nums):
            return self.contains_almost_duplicate(nums, t)
        if t == 0:
            return self.contains_nearby_duplicate(nums, k)
        
        # k < len(nums) and t != 0
        if k == 0:
            return False
        if k == 1:
            for i in range(len(nums)-1):
                if abs(nums[i+1] - nums[i]) <= t:
                    return True
            return False

剩下的其实就是将前面的解决方案整合。

首先我们看看对数组 nums 起始的 [ 0 , k + 1 ) [0, k+1) [0,k+1) 这一部分如何找到是否存在符合条件的两个数值(符合返回 True):

class Solution:
    [...省略代码...]

    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        [...省略代码...]

        # 2 =< k <=len(nums) and t != 0
        k = k + 1
        new_nums = sorted(nums[0:k])
        m = len(new_nums) // 2
        if new_nums[m] - new_nums[m-1] <= t or new_nums[m+1] - new_nums[m] <=t:
            return True

这里要注意的就是题目中说符合条件的两个数值的索引 i , j i, j i,j 只差不能大于 K K K,也就是可以等于 K,
所以为了符合 Python 中切片左闭右开,和 range 函数左闭右开的,在代码中为 K + 1 K+1 K+1。这也正是上面 [ 0 , K + 1 ) [0, K+1) [0,K+1) 表示方法的原因。

我们可以想到,只要对 nums[0:k] (从这里开始,k 都是 +1 处理过的)排序,然后做中位数和它上下一个数的差值和 t 比较就可以判断中 nums[0:k] 这个范围中是存在两个数值否符合问题条件。

我们在判断完 nums[0:k] 范围(不存在符合条件的两个数)之后,只要简单地将 nums[0] 从排过序的 new_nums 中剔除,然后将 nums[k] 加入到 new_nums 中(保证有序),就可以继续使用中位数和它上下比较来判断了。
然后再继续,剔除 nums[1],加入 nums[k+1],再做中位数判断。

所以后面的思路就是如此,重复上面的步骤即可,代码如下:

class Solution:
    [...省略代码...]

    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        [...省略代码...]

        # 2 =< k <=len(nums) and t != 0
        k = k + 1
        new_nums = sorted(nums[0:k])
        m = len(new_nums) // 2
        if new_nums[m] - new_nums[m-1] <= t or new_nums[m+1] - new_nums[m] <=t:
            return True
        
        for i in range(k, len(nums)):  # i => [k, length_of_nums)
            p = self.binary_search(new_nums, 0, len(new_nums), nums[i-k])
            new_nums[p] = nums[i]  # nums[k] -> nums[len(nums)-1]
            if p == 0:
                if new_nums[p] < new_nums[p+1]:
                    pass
                else:
                    self.bubble(new_nums, p, up=True)
            elif p == len(new_nums) - 1:
                if new_nums[p] > new_nums[p-1]:
                    pass
                else:
                    self.bubble(new_nums, p, up=False)
            else:
                # bobol sort once
                if new_nums[p] > new_nums[p+1]:
                    self.bubble(new_nums, p, up=True)
                elif new_nums[p] < new_nums[p-1]:
                    self.bubble(new_nums, p, up=False)

            # again
            if new_nums[m] - new_nums[m-1] <= t or new_nums[m+1] - new_nums[m] <=t:
                return True

        return False

虽然描述步骤不难,但是看到上面的代码还是不少。
首先在剔除 nums[0]nums[1],… 这样的值时,因为 new_nums 是排过序的,所以肯定是需要查找一下其值相应的位置(而且确定是可以找到的);代码中使用了二分查找,确保在测试的用例给的 K K K 值非常大时也能迅速找到,二分查找代码如下:

class Solution:
    [...省略代码...]
    
    def binary_search(self, nums, start, end, key):  # must in
        m = (end + start) // 2
        if nums[m] > key:
            return self.binary_search(nums, start, m, key)
        elif nums[m] < key:
            return self.binary_search(nums, m+1, end, key)
        else:
            return m
    
    def [...省略代码...]

另外一点就是二分查找到了位置,那么插入的时候,插入值会破坏排好序的数组!
这个时候就要用到最原始的排序算法 —— 冒泡排序,因为其它位置都是排好序的,所以只需要冒泡一次即可。
代码如下:

class Solution:
    [...省略代码...]

    def bubble(self, nums, p, up=True):
        if up:  # nums[p] > nums[p+1]
            i = p
            while i < len(nums)-1 and nums[i] > nums[i+1]:
                nums[i+1], nums[i] = nums[i], nums[i+1]
                i += 1
        else:
            i = p
            while i > 0 and nums[i] < nums[i-1]:
                nums[i-1], nums[i] = nums[i], nums[i-1]
                i -= 1
    
    def binary_search(self, nums, start, end, key):  # must in
        [...省略代码...]
    
    def [...省略代码...]

注意其中的 while 判断条件使用了短路运算的特性,省去了判断溢出的代码。

当然,边界条件是需要一直考虑到的,所以上面 containsNearbyAlmostDuplicate 代码中存在 if p == 0: ... elif p == len(new_nums) - 1: ... 这样的判断代码,不然直接判断 new_nums[p] > new_nums[p-1] 这种写法肯定是会出错的。

最后,因为只排序了一次 O ( k l o g ( k ) ) O(klog(k)) O(klog(k)),再加遍历一遍,遍历的时候也总是使用二分查找 O ( l o g k ) O(logk) O(logk)和冒泡,所以不严谨的总结复杂度不会超过 O ( n l o g k ) O(nlogk) O(nlogk),空间也只有 O ( k ) O(k) O(k),整个 Solution 还是很有效的:

n/a

其中第一次提交,因为最开始还是想用 hash 的解法,所以出现了 Time Limit Exceeded 的情况。



485. Max Consecutive Ones

n/a

Solution:

class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        _max = 0
        cnt = 0
        for i in nums:
            if i == 1:
                cnt += 1
            else:
                if cnt > _max:
                    _max = cnt
                cnt = 0
        return max(_max, cnt)

n/a



599. Minimum Index Sum of Two Lists

n/a
n/a

Solution:

class Solution:
    def findRestaurant(self, list1: List[str], list2: List[str]) -> List[str]:
        andy = {name: i for i, name in enumerate(list1)}
        doris = {name: i for i, name in enumerate(list2)}
        
        common = andy.keys() & doris.keys()
        common_index = []
        for _ in range(len(common)):
            item = common.pop()
            common_index.append((andy[item] + doris[item], item))

        index = min([_[0] for _ in common_index])
        ret = [_[1] for _ in common_index if _[0] == index]
        return ret

n/a



724. Find Pivot Index

n/a
n/a

Solution:

class Solution:
    def pivotIndex(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return -1
        if len(nums) == 1:
            return 0

        _sum = sum(nums)
        left_sum = 0
        for i in range(len(nums)):
            right_sum = _sum - left_sum - nums[i]
            if left_sum == right_sum:
                return i

            left_sum += nums[i]

        return -1

n/a



896. Monotonic Array - 是否为单调性数组

An array is monotonic if it is either monotone increasing or monotone decreasing.

An array A is monotone increasing if for all i <= j, A[i] <= A[j]. An array A is monotone decreasing if for all i <= j, A[i] >= A[j].

Return true if and only if the given array A is monotonic.

n/a
n/a

Solution:

class Solution:
    def isMonotonic(self, A: List[int]) -> bool:
        if len(A) <= 1:
            return True
        
        i = 0
        j = 1
        try:
            while A[j] == A[i]:
                j += 1
                i += 1
        except IndexError:
            return True
        
        if A[j] > A[i]:
            return self.is_monotonic_increasing(A, j, len(A) - 1)
        else:
            return self.is_monotonic_decreasing(A, j, len(A) - 1)

    def is_monotonic_increasing(self, A: list, start: int, end: int) -> bool:
        for i in range(start + 1, end + 1):
            if A[i] < A[i - 1]:
                return False
        return True
    
    def is_monotonic_decreasing(self, A: list, start: int, end: int) -> bool:
        for i in range(start + 1, end + 1):
            if A[i] > A[i - 1]:
                return False
        return True

n/a



使用归纳法解题

560. Subarray Sum Equals K - 计数所有连续子串等于 K

n/a

Solution:

分析过程已经写在注释中了。不过运行的速度和所有提交的对比结果比较意外。

毕竟我只遍历了两次而已,也就是 O ( n ) O(n) O(n) 的复杂度。可能是大部分 submit 使用的是 Leetcode 提供的 Solution 思路 - 遍历一次?

from collections import defaultdict

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        '''
        分析:
            len(nums) = n
            假设已知 [0,n-1) 中的 ret_1 值,以及以 nums[n-2] 结尾的所有连续子串和
            则 [0,n) 如果满足在连续字串和中可以找到值等于 k - nums[n-1] 的话,则 ret = ret_1 + 1
                否则 ret = ret_1
                
            对“结尾”的所有连续子串做反操作。
        '''
        
        d = defaultdict(int)
        i = -1
        right_sum = 0
        for _ in range(len(nums)):
            right_sum += nums[i]
            i -= 1
            d[right_sum] += 1

        ret = 0
        right_sum = 0
        i = -1
        for _ in range(len(nums)):
            curr = nums[i]

            diff = k - curr
            diff_index = diff + curr + right_sum
            ret += d[diff_index]

            d[curr + right_sum] = d[curr + right_sum] - 1  # remove this only-one sub-arr value
            i -= 1
            right_sum += curr

        return ret

n/a



快速排序应用(Partition)


905. Sort Array By Parity

Given an array A of non-negative integers, return an array consisting of all the even elements of A, followed by all the odd elements of A.

You may return any answer array that satisfies this condition.

Example 1:

Input: [3,1,2,4]
Output: [2,4,3,1]
The outputs [4,2,3,1], [2,4,1,3], and [4,2,1,3] would also be accepted.

Note:

  1. 1 <= A.length <= 5000
  2. 0 <= A[i] <= 5000

Solution:

class Solution:
    def sortArrayByParity(self, A: List[int]) -> List[int]:
        i, j = 0, len(A) - 1
        while i < j:
            if A[i] % 2 > A[j] % 2:
                A[i], A[j] = A[j], A[i]

            if A[i] % 2 == 0:
                i += 1
            if A[j] % 2 == 1:
                j -= 1
        return A

基本上这里就是使用了 partition 函数的原理。因为不要求偶数组和奇数组排好序,所以是 O ( n ) O(n) O(n)



643. Maximum Average Subarray I – 最大平均数子串

Given an array consisting of n integers, find the contiguous subarray of given length k that has the maximum average value. And you need to output the maximum average value.

Example 1:**

Input: [1,12,-5,-6,50,3], k = 4
Output: 12.75
Explanation: Maximum average is (12-5-6+50)/4 = 51/4 = 12.75

Note:

  1. 1 <= k <= n <= 30,000.
  2. Elements of the given array will be in the range [-10,000, 10,000].

Solution:

Approach #2 Sliding Window [Accepted]

class Solution:
    def findMaxAverage(self, nums: List[int], k: int) -> float:
        _max = sum(nums[:k])
        last_sum = _max
        offset = 1
        while (k + offset) <= len(nums):
            new_sum = last_sum - nums[offset - 1] + nums[k - 1 + offset]
            if _max < new_sum:
                _max = new_sum
            last_sum = new_sum
            offset += 1
        return _max / k



350. Intersection of Two Arrays II

n/a

Given two arrays, write a function to compute their intersection.

Example 1:

Input: nums1 = [1,2,2,1], nums2 = [2,2]
Output: [2,2]

Example 2:

Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
Output: [4,9]

Note:

  • Each element in the result should appear as many times as it shows in both arrays.
  • The result can be in any order.

Follow up:

  • What if the given array is already sorted? How would you optimize your algorithm?
  • What if nums1’s size is small compared to nums2’s size? Which algorithm is better?
  • What if elements of nums2 are stored on disk, and the memory is limited such that you cannot load all elements into the memory at once?

Solution:

from collections import defaultdict

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums_min, nums_max = (nums1, nums2) if len(nums1) < len(nums2) else (nums2, nums1)

        d = defaultdict(int)
        for i in nums_min:
            d[i] += 1

        ret = []
        for j in nums_max:
            if j in d.keys():
                ret.append(j)
                if d[j] > 1:
                    d[j] -= 1
                else:
                    d.pop(j)
        return ret

Submit:
n/a



888. Fair Candy Swap

2020/07/12

Alice and Bob have candy bars of different sizes: A[i] is the size of the i-th bar of candy that Alice has, and B[j] is the size of the j-th bar of candy that Bob has.

Since they are friends, they would like to exchange one candy bar each so that after the exchange, they both have the same total amount of candy. (The total amount of candy a person has is the sum of the sizes of candy bars they have.)

Return an integer array ans where ans[0] is the size of the candy bar that Alice must exchange, and ans[1] is the size of the candy bar that Bob must exchange.

If there are multiple answers, you may return any one of them. It is guaranteed an answer exists.

Example 1:

Input: A = [1,1], B = [2,2]
Output: [1,2]

Example 2:

Input: A = [1,2], B = [2,3]
Output: [1,2]

Example 3:

Input: A = [2], B = [1,3]
Output: [2,3]

Example 4:

Input: A = [1,2,5], B = [2,4]
Output: [5,4]

Note:

  • 1 <= A.length <= 10000
  • 1 <= B.length <= 10000
  • 1 <= A[i] <= 100000
  • 1 <= B[i] <= 100000
  • It is guaranteed that Alice and Bob have different total amounts of candy.
  • It is guaranteed there exists an answer.

Solution:

class Solution:
    def fairCandySwap(self, A: List[int], B: List[int]) -> List[int]:
        ans = []

        sa = sum(A)
        sb = sum(B)

        target = (sa - sb) // 2
        search_set = set(B)
        for a in A:
            # a - b = target
            b = a - target
            if b in search_set:
                return [a, b]

Submission: - N/A



双指针解决数组问题

👉 【Leetcode】#Easy# 双指针解决数组问题

674. Longest Continuous Increasing Subsequence

参见上述链接。

830. Positions of Large Groups

参见上述链接。



Reference



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值