LeetCode 算法笔记 数组篇(简单题)

566:重塑数组 (简单)数组

在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据。

给出一个由二维数组表示的矩阵,以及两个正整数rc,分别表示想要的重构的矩阵的行数和列数。

重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。

如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

示例 1:

输入: 
nums = 
[[1,2],
 [3,4]]
r = 1, c = 4
输出: 
[[1,2,3,4]]
解释:
行遍历nums的结果是 [1,2,3,4]。新的矩阵是 1 * 4 矩阵, 用之前的元素值一行一行填充新矩阵。

 

这道题目涉及的就是关于矩阵的reshape,python的numpy里面自带了reshape的函数,所以我们可以用这个函数去做,但是这里注意的是要进行类型的转换,即list与array之间的互相转换。具体的代码如下:

class Solution:
    def matrixReshape(self, nums, r, c):
        """
        :type nums: List[List[int]]
        :type r: int
        :type c: int
        :rtype: List[List[int]]
        """
        import numpy as np
        Anums = np.array(nums)
        try:
            Aresult = np.reshape(Anums,(r,c))
        except ValueError:
            Aresult = Anums
        result =  Aresult.tolist()
        return result

那么我们自己做的话,首先我是先把原来的矩阵手动转化为一个长列表,然后重新切分,具体代码如下:

class Solution:
    def matrixReshape(self, nums, r, c):
        """
        :type nums: List[List[int]]
        :type r: int
        :type c: int
        :rtype: List[List[int]]
        """
        if(len(nums)*len(nums[0])!=r*c):
            return nums
        list0 = []
        list1 = []
        for i in range(len(nums)):
            for j in range(len(nums[0])):
                list0.append(nums[i][j])
        for i in range(r):
            l = list0[i*c:(i+1)*c]
            list1.append(l)
        return list1

这个代码的计算时间就要远远快于上面的代码。这个的复杂度是O(n),n是矩阵中的元素个数。

888:公平的糖果交换 (简单)数组

爱丽丝和鲍勃有不同大小的糖果棒:A[i] 是爱丽丝拥有的第 i 块糖的大小,B[j] 是鲍勃拥有的第 j 块糖的大小。

因为他们是朋友,所以他们想交换一个糖果棒,这样交换后,他们都有相同的糖果总量。(一个人拥有的糖果总量是他们拥有的糖果棒大小的总和。)

返回一个整数数组 ans,其中 ans[0] 是爱丽丝必须交换的糖果棒的大小,ans[1] 是 Bob 必须交换的糖果棒的大小。

如果有多个答案,你可以返回其中任何一个。保证答案存在。

示例 4:

输入:A = [1,2,5], B = [2,4]
输出:[5,4]

首先对于这道题目,我们的思路其实是比较明确的,就是首先算出两个人总数的差,然后找到能弥补这个差的两个糖果就好了,所以一开始我的代码是这样的:

class Solution:
    def fairCandySwap(self, A, B):
        """
        :type A: List[int]
        :type B: List[int]
        :rtype: List[int]
        """
        sum0 = sum(A)
        sum1 = sum(B)
        dis = (sum0-sum1)//2
        for i in range(len(A)):
            for j in range(len(B)):
                if(A[i] - B[j]== dis):
                    return [A[i],B[j]]

但是这个代码超时了!很明显双重循环占用了使得复杂度变成了O(m*n),这里有一个巧妙的手法,也是常用的手法就是利用hash map,本来是一个穷搜的for循环用hash map之后,复杂度就变成了O(1),具体修改后的代码如下:

class Solution:
    def fairCandySwap(self, A, B):
        """
        :type A: List[int]
        :type B: List[int]
        :rtype: List[int]
        """
        sum0 = sum(A)
        sum1 = sum(B)
        dis = (sum0-sum1)//2
        setB = set(B)
        for x in A:
            if x - dis in setB:
                return[x,x - dis]

这个的复杂度就是O(m+n),所以巧妙的利用hash map是一个非常重要的手段。

448:找到所有数组中消失的数字 (简单)数组

给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:

输入:
[4,3,2,7,8,2,3,1]

输出:
[5,6]

这一道题目与上一题基本上思想一模一样,利用set的hash map特性,从而在O(n)时间内求解问题,具体代码如下:

class Solution:
    def findDisappearedNumbers(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        set0 = set(nums)
        result = []
        for i in range(len(nums)):
            if not i+1 in set0:
                result.append(i+1)
        return result

这个是非常高效,且完全符合题目要求的解法。

217:存在重复元素 (简单)数组 哈希表

给定一个整数数组,判断是否存在重复元素。

如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。

示例 1:

输入: [1,2,3,1]
输出: true

示例 2:

输入: [1,2,3,4]
输出: false

这道题目比较简单,直接用set就可以解决,代码如下:

class Solution:
    def containsDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        set0 = set(nums)
        if len(nums)>len(set0):
            return True
        else:
            return False

697:数组的度 (简单)数组

给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。

你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1:

输入: [1, 2, 2, 3, 1]
输出: 2
解释: 
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.

示例 2:

输入: [1,2,2,3,1,4,2]
输出: 6

对于这道题而言,一开始我的思路是比较死板的,就是想先算出度来,然后再去找这个度下的最短的,但是我发现这个想法实现起来十分的复杂,不过看了答案之后发现豁然开朗。首先答案的思路是最终的连续的子数组一定是左边元素和右边元素一样,且这个元素就是重复次数最多的那个元素。它的思路就是存下所有元素的最左边和最右边的位置,同时count出每一个元素的个数,具体实施过程中还是利用了字典这个hash map去做,具体的代码如下:

class Solution(object):
    def findShortestSubArray(self, nums):
        left, right, count = {}, {}, {}
        for i, x in enumerate(nums):
            if x not in left: left[x] = i #这个就是存储每个元素最左边的index
            right[x] = i                  #这个就是存储每个元素最右边的index
            count[x] = count.get(x, 0) + 1#这个就是count每个元素出现的次数

        ans = len(nums)
        degree = max(count.values())
        for x in count:
            if count[x] == degree:
                ans = min(ans, right[x] - left[x] + 1)

        return ans

所以说这样在O(N)的时间复杂度内就实现了整个计算。

830:较大分组的位置 (简单) 数组

在一个由小写字母构成的字符串 S 中,包含由一些连续的相同字符所构成的分组。

例如,在字符串 S = "abbxxxxzyy" 中,就含有 "a""bb""xxxx""z" 和 "yy" 这样的一些分组。

我们称所有包含大于或等于三个连续字符的分组为较大分组。找到每一个较大分组的起始和终止位置。

最终结果按照字典顺序输出。

示例 2:

输入: "abc"
输出: []
解释: "a","b" 和 "c" 均不是符合要求的较大分组。

示例 3:

输入: "abcdddeeeeaabbbcd"
输出: [[3,5],[6,9],[12,14]]

这一题与上一道题目在思想上是非常类似的,都是找子列的位置,所以同样我们用two index的方法去找,我们只要确定一个子列的左边和右边的位置,这个子列就算找出来了,而在中间我们只需要去找到合适的方法去吧这个子串的长度给算出来从而确定是否需要输出即可。具体的代码如下:

class Solution:
    def largeGroupPositions(self, S):
        """
        :type S: str
        :rtype: List[List[int]]
        """
        list0 = []
        count = 0
        left = 0
        right = 0
        for i in range(len(S)):
            if(S[right] != S[left]):
                if(right-left>=3):
                    list0.append([left,right-1])  
                left = i
            right = right + 1
        if(right-left>=3):
            list0.append([left,right-1])  
        return list0

所以说two point这个方法在针对很多这种数列的子数列的有些问题还是非常有用,只要你能够找到怎么去确定这个一头一尾然后去构造相关的关系。

674:最长连续递增序列 (简单) 数列

给定一个未经排序的整数数组,找到最长且连续的的递增序列。

示例 1:

输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。 

示例 2:

输入: [2,2,2,2,2]
输出: 1
解释: 最长连续递增序列是 [2], 长度为1。

注意:数组长度不会超过10000。

这道题目明显又是用two point的方法去做,因为又是找具有一定的性质的最长子序列,具体的代码如下:

class Solution:
    def findLengthOfLCIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        left = 0
        right = 0
        count = 0
        if(len(nums)==0):    #这个是用来处理特殊情况,空数组的情况
            return 0
        for i in range(len(nums)-1):
            if(nums[right]>=nums[right+1]):
                count = max(right-left+1,count)
                left = i+1
            right = right + 1
        count = max(count,right-left+1) #这个是用来处理最后一个序列的
        return count

基本上与上面题目的思想是一样的。

119. 杨辉三角 II (简单)数组

给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 行。

在杨辉三角中,每个数是它左上方和右上方的数的和。

示例:

输入: 3
输出: [1,3,3,1]

进阶:

你可以优化你的算法到 O(k) 空间复杂度吗?

这道题目首先我的想法是一个一个迭代的去更新,先从1到11,再到121,依次下去,但是这个里面从11到121好像又要用到循环,这个就很不好,而且这个关于列表定义的长度也有待商榷,但是论坛中有一个想法完美的解决了这个问题,其实121 = 011+110,1331 = 0121+1210,这个想法真的是太完美,这样我们只需要对于上一个数列在最前面加一个0,在最后面加一个0,相加就可以得到下一层的数,具体代码如下:

class Solution:
    def getRow(self, rowIndex):
        """
        :type rowIndex: int
        :rtype: List[int]
        """
        row = [1]
        for _ in range(rowIndex):
            row = [x + y for x, y in zip([0]+row, row+[0])]
        return row

当然它用了一个zip可以使得代码更加的简洁,不过这个思想真的很好。

66:加一(简单) 数组 数学

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。

示例 2:

输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。

这道题目就是数学上的加1,但是是对于数列而言的,考察的就是对数列的index的熟练运用,其实没有什么太多的算法思想,具体代码如下:

class Solution:
    def plusOne(self, digits):
        """
        :type digits: List[int]
        :rtype: List[int]
        """
        for i in range(len(digits)):
            if(digits[len(digits)-i-1]==9):
                continue
            else:
                digits[len(digits)-i-1] =  digits[len(digits)-i-1] + 1
                digits[len(digits)-i:] = [0]*i
                return digits
        a = [0]*len(digits)
        a.insert(0,1)
        return a

就是末尾如果有9就进位,所以就是看末尾到底有几个9,在全是9的前一位加1,后面全是0,如果全是9的话,就是100...然后前面加一位。

643:子数组最大平均数 I (简单)数列

给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。

示例 1:

输入: [1,12,-5,-6,50,3], k = 4
输出: 12.75
解释: 最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

这道题目是非常简单的,用滑窗去做,在O(n)的时间复杂度下可以很好的完成,具体代码如下:

class Solution:
    def findMaxAverage(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: float
        """
        result = sum(nums[0:k])
        for i in range(len(nums)-k+1):
            result = max(sum(nums[i:k+i]),result)
        return result/k

但是很不幸的是,这个代码超时了,其实这个的复杂度就是O(N),于是我们还是需要进一步思考有没有能够进一步简化的方法。再做算法的时候,我们很多时候想怎么提高这个计算的速度,尽量就是要避免重复运算,在这道题目里面,我们发现每i加一之后,我们都会去重新计算一下sum,但是其实这个是重复计算了,如果k为500,那么每次你要算500个数的和,但是我们可以用类似于队列的概念,把前面一个数给减掉,后面一个数给加上,这样原来的上面的复杂度是O(nk)变成了O(n),更新后代码如下:

class Solution:
    def findMaxAverage(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: float
        """
        result = sum(nums[0:k])
        sums = result
        for i in range(1,len(nums)-k+1):
            sums = sums - nums[i-1] + nums[i+k-1]
            result = max(sums,result)
        return result/k

这样这个就可以很好的通过。

219:存在重复元素 II (简单) 数组 哈希表

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true

示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

这一题如果把绝对值最大为k改为绝对值为k,那么这一题就非常简单了,直接一个for循环就可以解决问题。但是最大为k这个就很难描述。我们首先思考这么一个问题,我每进来一个数,我们就是要找它前面与它最近的那个数,看它们的index距离是多少,如果不大于k就ok,那么在这一步中最重要的就是把前面的数最近的index给保存下来,那么这个时候dict就常常喜欢干这种事,之前我们的双指针的方法也是基于这个思想去做的。如果你要存不同元素最新的位置,那么使用dict无疑是最好的办法。所以我们具体的代码如下:

class Solution:
    def containsNearbyDuplicate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: bool
        """
        dict0 = {}
        for i in range(len(nums)):
            try:
                if(i -dict0[nums[i]] <=k and i -dict0[nums[i]] > 0):
                    return True
                dict0[nums[i]] = i
            except KeyError:
                dict0[nums[i]] = i
        return False

这个的时间复杂度就是O(n)

605:种花问题 (简单) 数组

假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 。能否在不打破种植规则的情况下种入 朵花?能则返回True,不能则返回False。

示例 1:

输入: flowerbed = [1,0,0,0,1], n = 1
输出: True

示例 2:

输入: flowerbed = [1,0,0,0,1], n = 2
输出: False

注意:

  1. 数组内已种好的花不会违反种植规则。
  2. 输入的数组长度范围为 [1, 20000]。
  3. n 是非负整数,且不会超过输入数组的大小。

这一题我的想法就是找出该数列最大的能够植入的花的个数,然后与n比较,怎么找呢,其实我们发现能种的个数之和两个1之间0的个数有关,两个1之间3个0就是1个,5个0就是两个,所以我们的想法就是把连续的1的位置给count下来,这就是双指针的思路,不停寻找中间全是0两边是1的子列,把每个的子列能植入花的数目求出来,求个总和,当然这里要注意的是开头和结尾可能是一堆0,所以处理的时候要单独考虑一下,具体代码如下:

class Solution:
    def canPlaceFlowers(self, flowerbed, n):
        """
        :type flowerbed: List[int]
        :type n: int
        :rtype: bool
        """
        left = -2
        right = 0
        maxn = 0
        for i in range(len(flowerbed)):
            if(flowerbed[i]==1):
                maxn = maxn + (right-left -2)//2
                left = right
            right = right + 1
        maxn = maxn + (right-left -1)//2
        if(n>maxn):
            return False
        else:
            return True

算法的复杂度就是O(n)

532:数组中的K-diff数对 (简单) 数组 双指针

给定一个整数数组和一个整数 k, 你需要在数组里找到不同的 k-diff 数对。这里将 k-diff 数对定义为一个整数对 (i, j), 其中 i  j 都是数组中的数字,且两数之差的绝对值是 k.

示例 1:

输入: [3, 1, 4, 1, 5], k = 2
输出: 2
解释: 数组中有两个 2-diff 数对, (1, 3) 和 (3, 5)。
尽管数组中有两个1,但我们只应返回不同的数对的数量。

示例 2:

输入:[1, 2, 3, 4, 5], k = 1
输出: 4
解释: 数组中有四个 1-diff 数对, (1, 2), (2, 3), (3, 4) 和 (4, 5)。

示例 3:

输入: [1, 3, 1, 5, 4], k = 0
输出: 1
解释: 数组中只有一个 0-diff 数对,(1, 1)。

注意:

  1. 数对 (i, j) 和数对 (j, i) 被算作同一数对。
  2. 数组的长度不超过10,000。
  3. 所有输入的整数的范围在 [-1e7, 1e7]。

一开始我想用hash table来做,思路就是利用set,我们建立遍历前数据的这样一个集合,然后每进来一个数据,我们就看这个数是否在前面的集合中,如果不在就和集合中的数进行比较看是否有和自己相差k的,然后更新集合,如果在了,那么就下一个数,因为这样的遍历会导致在原先集合中有的数,如果它们之间的距离是k的话,肯定已经被计算过了,所以说这样的遍历是没有遗漏的,而且计算复杂度也不高大概就O(n),具体的代码如下:

class Solution:
    def findPairs(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        set0 = set()
        result = 0
        if(k<0):
            return 0
        if k == 0:
            c = collections.Counter(nums)
            for i in c:
                if c[i] > 1:
                    result = result + 1
            return result       
        for i in range(len(nums)):
            if(not nums[i] in set0):
                if(nums[i] + k in set0):
                    result = result + 1
                if(nums[i] - k in set0):
                    result = result + 1
                set0.add(nums[i])
        return result

一开始这个思想中对于k=0这个点我实再是找不到很好的方法去解决,然后看了论坛之后用collections.Counter去做,发现可以比较好的去解决这个问题。不过利用hash map这个思想下面的代码会好很多:

def findPairs(self, nums, k):
        res = 0
        c = collections.Counter(nums)
        for i in c:
            if k > 0 and i + k in c or k == 0 and c[i] > 1:
                res += 1
        return res

665:非递减数列 (简单)数列

给定一个长度为 n 的整数数组,你的任务是判断在最多改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中所有的 i (1 <= i < n),满足 array[i] <= array[i + 1]

示例 1:

输入: [4,2,3]
输出: True
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。

示例 2:

输入: [4,2,1]
输出: False
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。

我是从两个方面去考虑这个问题,首先要能够在最多改变一个元素的基础上,所以最多只有一次出现相邻两个数,前一个a大于后面一个b的情况。在只出现一次的情况下我们可以有两种解决办法,一个是减小大的那个数a,这个就要求a前面的那个数小于a后面的那个数。或者说我们增加小的那个数b,这个就要求b前面的那个数小于b后面的那个数,这两种情况只要有一种达到就可以。代码如下:

class Solution:
    def checkPossibility(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        count = 0
        l = []
        for i in range(len(nums)-1):
            if (nums[i]>nums[i+1]):
                l.append(i)
                count = count + 1
        print(count)
        if count > 1:
            return False
        else:
            if(count == 1 and l[0]>0 and l[0]+2 < len(nums)):
                if(nums[l[0]-1] > nums[l[0]+1] and nums[l[0]] > nums[l[0]+2]):
                    return False
            return True

答案上给出了三种方法,brute force我们就不说了,第二种方法的代码如下:

class Solution(object):
    def checkPossibility(self, A):
        def brute_force(A):
            #Same as in approach 1

        i, j = 0, len(A) - 1
        while i+2 < len(A) and A[i] <= A[i+1] <= A[i+2]:
            i += 1
        while j-2 >= 0 and A[j-2] <= A[j-1] <= A[j]:
            j -= 1

        if j - i + 1 <= 2:
            return True
        if j - i + 1 >= 5:
            return False

        return brute_force(A[i: j+1])

具体的思想就是把两边符合非递减要求的子列给删掉,然后对剩下的子列进行处理,如果子列长度很长,大于等于5,那就说明至少有两个是反的,那就是false,否则就可以使用暴力解法去做,因为子列最长为4,所以是O(1)时间。最后一个方法和我的思想是一样的。

 

你可能感兴趣的:(LeetCode 算法笔记 数组篇(简单题))