【算法】數組

1 數組理論基礎

數組是存放在連續內存空間上的相同類型數據的集合。

  • 數組下標都是從0開始的
  • 數組內存空間的地址是連續的

在刪除或者增添元素時,需要移動其他元素的地址:

C++要注意vector 和 array的區別,vector的底層實現是array,嚴格來講vector是容器,不是數組。

數組的元素是不能刪的,只能覆蓋。

二維數組在內存的空間地址:

  • C++中二維數組在地址空間上是連續的

  • Java沒有指針,不暴露元素的地址,尋址操作完全交給虛擬機

2 二分查找

題目:給定一個 n 個元素有序的(升序)整型數組 nums 和一個目標值 target ,寫一個函數搜索 nums 中的 target,如果目標值存在返回下標,否則返回 -1。

示例1

輸入: nums = [-1,0,3,5,9,12], target = 9
輸出: 4

示例2

輸入: nums = [-1,0,3,5,9,12], target = 2
輸出: -1

提示:

  1. 你可以假設 nums 中的所有元素是不重複的。
  2. n 將在 [1, 10000]之間。
  3. nums 的每個元素都將在 [-9999, 9999]之間。

思路

有序數組、無重複元素 ⇒ 可以使用二分法

邊界條件:根據區間定義來操作,遵循”循環不變量“規則

  1. 區間定義一:左閉右閉[left, right]
  • while (left <= right) 要使用 <= ,因爲left == right是有意義的,所以使用 <=
  • if (nums[middle] > target) right 要賦值爲 middle - 1,因爲當前這個nums[middle]一定不是target,那麼接下來要查找的左區間結束下標位置就是 middle - 1

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        while left <= right:
            middle = left + (right - left) //2
            if nums[middle] > target:
                right = middle -1
            elif nums[middle] < target:
                left = middle + 1
            else:
                return middle
        return -1

時間複雜度:O(log n),空間複雜度:O(1)

  1. 區間定義二:左閉右開[left, right)
  • while (left < right),這裏使用 < ,因爲left == right在區間[left, right)是沒有意義的
  • if (nums[middle] > target) right 更新爲 middle,因爲當前nums[middle]不等於target,去左區間繼續尋找,而尋找區間是左閉右開區間,即:下一個查詢區間不會去比較nums[middle]

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)
        while left < right:
            middle = left + ((right - left) >> 1)
            if nums[middle] > target:
                right = middle
            elif nums[middle] < target:
                left = middle + 1
            else:
                return middle
        return -1

時間複雜度:O(log n),空間複雜度:O(1)

3 移除元素

題目

給你一個數組 nums 和一個值 val,你需要原地移除所有數值等於 val 的元素,並返回移除後數組的新長度。

不要使用額外的數組空間,你必須僅使用 O(1) 額外空間並原地修改輸入數組。

元素的順序可以改變。你不需要考慮數組中超出新長度後面的元素。

示例1

輸入:nums = [3,2,2,3], val = 3
輸出:2, nums = [2,2]

示例2

輸入:nums = [0,1,2,2,3,0,4,2], val = 2
輸出:5, nums = [0,1,4,0,3]

說明

請注意,輸入數組是以**「引用」**方式傳遞的,這意味着在函數裏修改輸入數組對於調用者是可見的。

思路

1. 暴力解法,一個for循環遍歷數組元素 ,第二個for循環更新數組

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        size,i = len(nums), 0
        while i < size:
            if nums[i] == val: # 發現需要移除的元素,就將數組集體向前移動一位
                for j in range(i+1, size): 
                    nums[j-1] = nums[j]
                size -= 1 # 此時數組的大小-1
                i -= 1 # 因爲下標i以後的數值都向前移動了一位,所以i也向前移動一位
            i += 1
        return size

時間複雜度:O($n^2$),空間複雜度:O(1)

2. 快慢雙指針

  • 快指針:尋找新數組的元素 ,新數組就是不含有目標元素的數組
  • 慢指針:指向更新新數組下標的位置
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        fastIndex, slowIndex = 0, 0 # 快慢指針
        while fastIndex < len(nums):
             # slow 用來收集不等於 val 的值
            if nums[fastIndex] != val:
                nums[slowIndex] = nums[fastIndex]
                slowIndex += 1
            fastIndex += 1
        return slowIndex

時間複雜度:O(n),空間複雜度:O(1)

3. 相向雙指針方法,改變元素相對位置,數組中出現一個val,右指針左移一次

  • 如果左指針指向的元素等於 val,此時將右指針指向的元素複製到左指針的位置,然後右指針左移一位。重複直到左指針指向的元素的值不等於 val爲止。當左指針和右指針重合的時候,左右指針遍歷完數組中所有的元素。
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left, right = 0, len(nums)
        while left < right:
            if nums[left] == val:
                nums[left] = nums[right -1]
                right -= 1
            else:
                left += 1
        return left

時間複雜度:O(n),空間複雜度:O(1)

  • 只移動右邊不等於val的元素
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left, right = 0, len(nums)
        while left < right:
            if nums[left] == val: # 找左邊等於val的元素
                while left < right and nums[right-1] == val: # 找右邊不等於val的元素
                    right -= 1
                nums[left] = nums[right -1] # 將右邊不等於val的元素覆蓋左邊等於val的元素
                right -= 1
            else:
                left += 1
        return left # left一定指向了最終數組末尾的下一個元素

4 有序數組的平方

題目:給你一個按非遞減順序排序的整數數組 nums,返回每個數字的平方組成的新數組,要求也按非遞減順序排序。

示例1

輸入:nums = [-4,-1,0,3,10]
輸出:[0,1,9,16,100]

示例2

輸入:nums = [-7,-3,2,3,11]
輸出:[4,9,9,49,121]

思路

1. 暴力排序

class Solution:
    def sortedSquares(self, nums :List[int]) -> List[int]:
        for i in range(len(nums)):
            nums[i] *= nums[i]
        nums.sort()
        return nums

時間複雜度是 O(n + nlogn), 可以說是O(nlogn)的時間複雜度,空間複雜度是O(1)

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        return sorted(x*x for x in nums)

2. 雙指針

數組其實是有序的, 只不過負數平方之後可能成爲最大數了。那麼數組平方的最大值就在數組的兩端,不是最左邊就是最右邊,不可能是中間。

class Solution:
    def sortedSquares(self, nums :List[int]) -> List[int]:
        idx = len(nums) - 1
        l, r, k = 0, idx, idx
        res = [0] * len(nums) # 需要提前定義列表,存放結果
        while l <= r:
            if nums[l] ** 2  < nums[r] ** 2: # 左右邊界進行對比,找出最大值
                res[k] = nums[r] ** 2
                r -= 1
            else:
                res[k] = nums[l] **2
                l += 1
            k -= 1 # 存放結果的指針需要往前平移一位
        return res

時間複雜度O(n),空間複雜度O(n)

5 長度最小的子數組**

題目:給定一個含有 n ****個正整數的數組和一個正整數 target 

找出該數組中滿足其和 ****≥ target **的長度最小的 連續子數組 [numsl, numsl+1, ..., numsr-1, numsr] ,並返回其長度。**如果不存在符合條件的子數組,返回 0 。

示例 1:

輸入:target = 7, nums = [2,3,1,2,4,3]
輸出:2

示例 2:

輸入:target = 4, nums = [1,4,4]
輸出:1

示例 3:

輸入:target = 11, nums = [1,1,1,1,1,1,1,1]
輸出:0

提示:

  • 1 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

思路

1. 兩個for循環暴力求解(會超時)

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        l = len(nums)
        minLen = float('inf')
        for i in range(l):
            sum = 0
            for j in range(i, l):
                sum += nums[j]
                if sum >= target:
                    minLen = min(minLen, j - i + 1)
                    break # 因爲是找符合條件的最短子序列,一旦符合條件就break
        return 0 if minLen == float('inf') else minLen

時間複雜度:O($n^2$),空間複雜度:O(1)

2. 滑動窗口(雙指針),不斷調節子序列的起始位置和終止位置,從而得出我們要想的結果

窗口就是滿足其和 ≥ target 的長度最小的連續子數組。

窗口的起始位置如何移動:如果當前窗口的值大於target了,窗口就要向前移動了。

窗口的結束位置如何移動:窗口的結束位置就是遍歷數組的指針,也就是for循環裏的索引。

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # 快慢指針代表#了滑動窗口起始位置
        slow, fast, l = 0, 0, len(nums)
        minLen = float('inf')
        sum = 0 # 滑動窗口數值之和
        while fast < l: 
            sum += nums[fast]
            while sum >= target:
                minLen = min(minLen, fast - slow + 1)
                sum -= nums[slow]  # 這裏體現出滑動窗口的精髓之處,不斷變更子序列的起始位置      
                slow += 1  
						fast += 1     
        return 0 if minLen == float('inf') else minLen

時間複雜度:O(n),空間複雜度:O(1)

3. 前綴和+二分查找

爲了使用二分查找,需要額外創建一個數組 sums 用於存儲數組 nums 的前綴和。得到前綴和之後,對於每個開始下標 i,可通過二分查找得到大於或等於 i 的最小下標 bound,使 sums[bound]−sums[i]≥s,並更新子數組的最小長度。

這道題保證了數組中每個元素都爲正,所以前綴和一定是遞增的,這一點保證了二分的正確性。如果題目沒有說明數組中每個元素都爲正,這裏就不能使用二分來查找這個位置了。

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        if not nums:
            return 0
    l = len(nums)
    minLen = float('inf')
    sums = [0]
    for i in range(l):
        sums.append(sums[-1] + nums[i]) # sums[i]代表nums[i]前面所有元素之和

    # l --- r     sum[r+1] - sum[l] &gt;= target
    # sum[r+1] &gt;= sum[l] + target
    for i in range(l):
        s = target + sums[i]
        bound = bisect.bisect_left(sums, s) # 二分查找sums裏&ge;s的第一個位置
        if bound != len(sums): # sums沒有比s大的(即沒有符合條件
            minLen = min(minLen, bound - i)
            
    return 0 if minLen == float('inf') else minLen

6 螺旋矩陣

題目:給你一個正整數 n ,生成一個包含 1 到 n^2 所有元素,且元素按順時針順序螺旋排列的 n x n 正方形矩陣 matrix 。

示例 1:

輸入:n = 3
輸出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

輸入:n = 1
輸出:[[1]]

提示:

  • 1 <= n <= 20

思路

模擬順時針畫矩陣的過程:

  • 填充上行從左到右
  • 填充右列從上到下
  • 填充下行從右到左
  • 填充左列從下到上

注意邊界條件!

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        ans = [[0] * n for _ in range(n)]
        l, r, t, b = 0, n - 1, 0, n - 1 # 左右上下邊界
        curr = 1
    while curr &lt;= n**2:
        for i in range(l, r + 1): # 正在從左到右填充上行
            ans[t][i] = curr
            curr += 1
        t += 1

        for i in range(t, b + 1): # 正在從上到下填充下行
            ans[i][r] = curr
            curr += 1
        r -= 1

        for i in range(r, l - 1, -1): # 正在從右到左填充下行
            ans[b][i] = curr
            curr += 1
        b -= 1

        for i in range(b, t - 1, -1): # 正在從下到上填充左行
            ans[i][l] = curr
            curr += 1
        l += 1
        
    return ans

時間複雜度 O($n^2$)::模擬遍歷二維矩陣的時間,空間複雜度 :O($n^2$)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章