Leetcode刷题:初级算法之数组

初级算法-数组

    • 从排序数组中删除重复项
    • 买卖股票的最佳时机 II
    • 存在重复
    • 两个数组的交集 II
    • 加一
    • 移动零
    • 两数之和
    • 旋转图像
    • 有效的数独
    • 小结

题目来源:https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/1/array/

从排序数组中删除重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2],

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。

你不需要考虑数组中超出新长度后面的元素。
示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。
说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}

very easy,用了双指针

class Solution(object):
    def removeDuplicates(self, nums):
        # 处理类似 [1], []
        if len(nums) <= 1:
            return len(nums)
        
        idx = 0
        for i in range(1, len(nums)):
            if nums[i] != nums[idx]:
                idx += 1
                nums[idx] = nums[i]
        return idx+1

买卖股票的最佳时机 II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

开始的思路:每找到一个升序的子数组就将数组首尾差值(首:买入;尾:卖出)加到总数中;
但是这个程序的鲁棒性并不好,首先需要判断数组是不是为空,其次还需要在最后去处理【1,2,3】和【2,1,3,4,5】这种升序子数组在末端的例子。

class Solution(object):
    def maxProfit(self, prices):
        if len(prices) == 0:
            return 0

        idx1 = 0
        sum = 0
        n = len(prices)
        for idx2 in range(0, n - 1):
            if prices[idx2] >= prices[idx2 + 1]:
                sum += (prices[idx2] - prices[idx1])
                idx1 = idx2 + 1

        if idx1 != n - 1:
            sum += (prices[n - 1] - prices[idx1])

        return sum

好的解法是:只要后面的比前面的大就把他们的差值加入到总数中;
思路的受限主要来源于这个问题的背景,想着要找到买入和卖出的时间,然后计算出这两个位置股票的差值,然而忘记了其实他们的差值就正好=这个升序数组中每两个相邻数的差的总和;这样程序的鲁棒性也会增强!

class Solution(object):
    def maxProfit(self, prices):
        sum = 0
        n = len(prices)
        for i in range(0, n -1):
            if prices[i] < prices[i+1]:
                sum += prices[i+1] - prices[i]
        return sum

存在重复

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

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

示例 1:

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

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

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

直接用set的特性

直接用

class Solution(object):
    def containsDuplicate(self, nums):
        s = set()
        for num in nums:
            s.add(num)
            
        if len(s) != len(nums):
            return True
        else:
            return False

两个数组的交集 II

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:

输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致
我们可以不考虑输出结果的顺序

思路:
将数组1中的数存到字典里面:key是这个数,value是它出现的次数
然后遍历数组2,如果这个数在字典里面,而且其对应的value不是0(比如 【1,2,2】,nums2 = 【2,2,2,3】那么到第三个2的时候就不会append了,因为此时dic[2] = 0),就在我们最后把这个数append到要返回的数组
计算复杂度:o(n),空间复杂度:o(n)

class Solution(object):
    def intersect(self, nums1, nums2):
        dic = {}
        # create a dictionary where 
        # key: number in a list
        # value: ocurring times of this number
        for n in nums1:
            if n in dic:
                dic[n] += 1
            else:
                dic[n] = 1
        res_list = []
        for n in nums2:
            if n in dic and dic[n] != 0:
                res_list.append(n)
                dic[n] -= 1
        return res_list

进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

加一

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

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

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

示例 1:

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

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

思路: 考虑到类似19,99这样的数就可以了

class Solution(object):
    def plusOne(self, digits):
        fw = 1
        for i in range(len(digits)-1, -1, -1):
            if digits[i]+fw == 10:
                digits[i] = 0
                fw = 1
            else:
                digits[i] += fw
                return digits
        return [1] + digits 

移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

思路: 把0想象成一个小泡泡不断往后冒~~~(实际上,不用这么麻烦,可以更简单)

class Solution(object):
    def moveZeroes(self, nums):
        # 找到第一个零的位置
        idx = 0
        while idx < len(nums):
            if nums[idx] == 0:
                break
            idx += 1
            
        if idx >= len(nums)-1:
            return
        
        for i in range(idx+1, len(nums)):
            if nums[i] != 0:
                # 0和非0数互换位置
                nums[idx] = nums[i]
                nums[i] = 0
                idx += 1

更优雅的思路(没必要去找第一0所在的位置):

class Solution(object):
    def moveZeroes(self, nums):
        idx = 0
        for i in range(len(nums)):
            if nums[i] != 0:
                # 0和非0数互换位置  
                tmp = nums[idx]
                nums[idx] = nums[i]
                nums[i] = tmp
                idx += 1

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路:
一开始的思路是,遍历两遍,第一遍将数组中的数和它的index存到字典中,然后第二遍看有没有key = target - 当前数
但是被自己否决了,认为如果存在相同的数,那么没办法存,可是却忽略了重要的条件:每种输入只会对应一个答案这也就意味着,就算有相同的,如果它是答案的话,那只有可能是类似于target = 8,数组中有两个4这种情况;
然而,其实这种担忧是不必的,因为我们用一遍遍历就可以完成了,时间复杂度为o(n)(上述提到的特殊情况也得到了解决)

class Solution(object):
    def twoSum(self, nums, target):
        dic = {}
        for i in range(len(nums)):
            if target - nums[i] in dic:
                return [dic[target - nums[i]]] + [i] 
            else:
                dic[nums[i]] = i

旋转图像

给定一个 n × n 的二维矩阵表示一个图像。

将图像顺时针旋转 90 度。

说明:

你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:

给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],

原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:

给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],

原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]

解法一(也是我自己想到的思路): 通过找规律发现就是四个元素的链子顺时针旋转一次
即,原来位置在 (i, j) 的元素转到了 (j, N-1-i) 的位置
用公式表示就是 Matrix[j, N-1-i] <= Matrix [i, j]
用图表示就是这样滴:

Leetcode刷题:初级算法之数组_第1张图片

于是我就编了下面的代码,一看就很不美观? 主要原因是我利用了集合来存还没有被修改过的节点;在赋值的时候不够简单有效

class Solution(object):
    def rotate(self, matrix):
        N = len(matrix)
        notVisited = set(range(N*N))

        for iter in range(N * N // 4):
			# 所有的都被访问过了
            if not notVisited:
                return

            n = notVisited.pop()
            i, j = n//N, n - (n//N) * N
            
            # step 1
            tmp1 = matrix[j][N-1-i]
            matrix[j][N - 1 - i] = matrix[i][j]
            i, j = j, N - 1 - i
            
            notVisited.remove(i * N + j)

            # step 1
            tmp2 = matrix[j][N-1-i]
            matrix[j][N - 1 - i] = tmp1
            i, j = j, N - 1 - i
            
            notVisited.remove(i * N + j)

            # step 1
            tmp1 = matrix[j][N-1-i]
            matrix[j][N - 1 - i] = tmp2
            i, j = j, N - 1 - i
            
            notVisited.remove(i * N + j)

            # step 1
            matrix[j][N - 1 - i] = tmp1
            i, j = j, N - 1 - i
            

整理过后,更优化的代码如下:

说明: Matrix[j, N-1-i] <= Matrix [i, j] 可以看作是 Matrix[i, j] <= Matrix [N-1-j, i]
为什么设定i和j的取值分别在[0, n/2), [i, n-i-1] 呢??????

class Solution(object):
    def rotate(self, matrix):
        N = len(matrix)
        for i in range(N//2):
            for j in range(i, N-i-1):
                tmp = matrix[i][j]
                matrix[i][j] = matrix[N-j-1][i]
                matrix[N-j-1][i] = matrix[N-i-1][N-j-1]
                matrix[N-i-1][N-j-1] = matrix[j][N-i-1]
                matrix[j][N-i-1] = tmp

但这个不是最优雅的方法,还有 解法2和 解法3,都是利用了矩阵的旋转,非常elegant!!!!!!!!感觉是一道数学题没错了

/*
 * clockwise rotate
 * first reverse up to down, then swap the symmetry 
 * 1 2 3     7 8 9     7 4 1
 * 4 5 6  => 4 5 6  => 8 5 2
 * 7 8 9     1 2 3     9 6 3
*/
void rotate(vector<vector<int> > &matrix) {
    reverse(matrix.begin(), matrix.end());
    for (int i = 0; i < matrix.size(); ++i) {
        for (int j = i + 1; j < matrix[i].size(); ++j)
            swap(matrix[i][j], matrix[j][i]);
    }
}

/*
 * anticlockwise rotate
 * first reverse left to right, then swap the symmetry
 * 1 2 3     3 2 1     3 6 9
 * 4 5 6  => 6 5 4  => 2 5 8
 * 7 8 9     9 8 7     1 4 7
*/
void anti_rotate(vector<vector<int> > &matrix) {
    for (auto vi : matrix) reverse(vi.begin(), vi.end());
    for (int i = 0; i < matrix.size(); ++i) {
        for (int j = i + 1; j < matrix[i].size(); ++j)
            swap(matrix[i][j], matrix[j][i]);
    }
}

代码来源:https://leetcode.com/problems/rotate-image/discuss/18872/A-common-method-to-rotate-the-image

有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

示例 1:

输入:
[
[“5”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: true
示例 2:

输入:
[
[“8”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。

思路: 因为这道题不需要验证数独是不是可解的,所以用上面的三条规则实现简单的逻辑即可,代码如下:

class Solution(object):
    def isValidSudoku(self, board):
        """
        :type board: List[List[str]]
        :rtype: bool
        """
        check = set()
        # check rows
        for i in range(9):
            check.clear()
            for j in range(9):
                if board[i][j] != '.':
                    if board[i][j] in check:
                        return False
                    else:
                        check.add(board[i][j])
     
        # check cols
        for j in range(9):
            check.clear()
            for i in range(9):
                if board[i][j] != '.':
                    if board[i][j] in check:
                        return False
                    else:
                        check.add(board[i][j])
        
        # check 3x3 box
        for bx in range(3):
            for by in range(3):
                check.clear()
                for i in range(bx*3, bx*3+3):
                    for j in range(by*3, by*3+3):
                        if board[i][j] != '.':
                            if board[i][j] in check:
                                return False
                            else:
                                check.add(board[i][j])
        return True

小结

自此,初级算法的数组部分全部结束。

总结一下,题目并不算特别难,主要考察的是逻辑的实现,以及边界值是否有考虑。

同时,还训练了如何去优化算法,以获得更优的计算复杂性&空间复杂性;这些优化主要需要从数据结构中找一些思路,比如利用集合、字典等的实现;还有一些优化是基于对这个问题的更一般性的规律来说,从而找到更好的算法~

Keep Fighting!!!

你可能感兴趣的:(刷题)