Leecode——基础算法练习

 

一、二分搜索

搜索模板:O(logn),对于有序数组查找
  1. 初始化:start=0,end=len-1
  2. 循环退出条件:start+1<end
  3. 比较中点和目标值:A[mid]==、<、>目标值
  4. 判断最后两个元素:A[start]、A[end]与目标值
搜索:
1.搜索目标范围
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        start, end, mark = 0, len(nums) - 1, -1
        while start <= end:
            mid = int((start + end)/2)
            if nums[mid] == target:
                mark = mid
                break
            elif nums[mid] < target:
                start = mid + 1
            else:
                end = mid - 1
        if mark == -1:
            return [-1, -1]
        else:
            start = end = mark
            while end < len(nums):
                if nums[end] != target:
                    break
                end += 1
            while start >= 0:
                if nums[start] != target:
                    break
                start -= 1
            return [start + 1, end - 1]

2.搜索目标

    def searchInsert(self, nums: List[int], target: int) -> int:
        #要先判断target是否在范围内
        if target < nums[0]:
            return 0
        left, right = 0, len(nums)
        #循环l<r(最后取的区间会含1个元素)与l<=r(最后区间元素为空)
        while left < right:
            #严格限制了mid的计算结果为整型
            mid = (left + right) // 2
            if nums[mid] < target:
                left = mid + 1
            else:
                right = mid            
        return left

(法2)    

    def searchInsert(self, nums: List[int], target: int) -> int:
        start, end, mark = 0, len(nums) - 1, 0
        while start + 1 < end:
            mid = (start + end) // 2
            if nums[mid] < target:
                start = mid
            else:
                end = mid
        # 剩余start和end,必在这两个位置左右
        if nums[start] >= target:
            mark = start
        elif nums[end] >= target:
            mark = end
        else:
            mark = end + 1
        return mark   

3.从数组中搜索目标    

    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        if not matrix:
            return False
         # 先确定行
        start, end, mark = 0, len(matrix) - 1, 0
        while start + 1 < end:
            mid = (start + end) // 2
            temp = matrix[mid]
            if matrix[mid][len(temp)-1] < target:
                start = mid
            else:
                end = mid
        if matrix[start][0] <= target and matrix[start][len(matrix[start]) - 1] >= target:
            mark = start
        elif matrix[end][0] <= target and matrix[end][len(matrix[end]) - 1] >= target:
            mark = end
        else:
            return False
        # 对行找数
        start, end = 0, len(matrix[mark]) - 1
        while start + 1 < end:
            mid = (start + end) // 2
            if matrix[mark][mid] < target:
                start = mid
            else:
                end = mid
        if matrix[mark][start] == target or matrix[mark][end] == target:
            return True
        else:
            return False

(法2:降多维为1维)

旋转数组:

1.找出旋转数组中的最小值

    def findMin(self, nums: List[int]) -> int:
        if not nums:
            return 0
        start, end = 0, len(nums) - 1
        while start + 1 < end:
            mid = (start + end) // 2
            # 判断912345678,中间大说明最小在右侧,否则在左侧
            if nums[mid] > nums[end]:
                start = mid
            else:
                end = mid
        if nums[start] < nums[end]:
            return nums[start]
        else:
            return nums[end]

2.找出有重复的旋转数组中的最小值

    def findMin(self, nums: List[int]) -> int:
        # 一般情况和无重复一样,中点与最右值比较,再移动端点
        # 有重复值需要考虑222221222的情况:
        # 此时最右与中点一样,无法判断情况,谨慎考虑:移动端点
        if not nums:
            return 0
        start, end = 0, len(nums) - 1
        while start + 1 < end:
            mid = (start + end) // 2
            if nums[mid] > nums[end]:
                start = mid
            elif nums[mid] < nums[end]:
                end = mid
            else:
                end -= 1
        if nums[start] < nums[end]:
            return nums[start]
        else:
            return nums[end]

3.在旋转数组中搜索数

    def search(self, nums: List[int], target: int) -> int:
        start, end = 0, len(nums) - 1
        while start + 1 < end:
            mid = (start + end) // 2
            # 在中点右侧是递增情况下
            if nums[mid] < nums[end]:
                if nums[end] >= target and nums[mid] <= target:
                    start = mid
                else:
                    end = mid
            # 中点左侧是递增
            else:
                if nums[start] <= target and nums[mid] >= target:
                    end = mid
                else:
                    start = mid
        if nums[start] == target:
            return start
        elif nums[end] == target:
            return end
        else:
            return -1

4.在有重复的旋转数组中搜索数

    def search(self, nums: List[int], target: int) -> bool:
        start, end, mark = 0, len(nums) - 1, -1
        while start + 1 < end:
            mid = (start + end) // 2
            # 有重复的旋转数组中存在3种情况
            # 当中点小于右点,说明中点右侧是递增的
            if nums[mid] < nums[end]:
                if nums[mid] <= target and nums[end] >= target:
                    start = mid
                else:
                    end = mid
            # 当中点大于右点,说明中点左侧的递增的
            elif nums[mid] > nums[end]:
                if nums[start] <= target and nums[mid] >= target:
                    end = mid
                else:
                    start = mid
            # 当中点等于右点时,无法判定哪侧递增2222122
            else:
                if nums[end] == target:
                    mark = end
                    break
                else:
                    end -= 1
        if mark != -1 or nums[start] == target or nums[end] == target:
            return True
        else:
            return False        

二、排序算法

插入排序
选择排序
冒泡排序
  • 快速排序
    def sortArray(self, nums: List[int]) -> List[int]:
        self.quickSort(nums, 0, len(nums) - 1)
        return nums

    def quickSort(self, nums:List[int], start:int, end:int):
        if start < end:
            # 快排核心:找出中轴,中轴左边与右边各自分治(中轴已站定,不需要动)
            pivot = self.partition(nums, start, end)
            self.quickSort(nums, start, pivot - 1)
            self.quickSort(nums, pivot + 1, end)

    def partition(self, nums:List[int], start:int, end:int) -> int:
        mark = nums[end]
        # 找中轴:用i记录中轴位置,用j来循环数组
        i = j = start
        for j in range(start, end):
            if nums[j] < mark:
                self.swap(nums, i, j)
                i += 1
        self.swap(nums, i, end)
        return i

    def swap(self, nums:List[int], i:int, j:int):
        temp = nums[j]
        nums[j] = nums[i]
        nums[i] = temp
  • 归并排序
    def sortArray(self, nums: List[int]) -> List[int]:
        return self.mergeSort(nums)
    
    def mergeSort(self, nums:List[int]) -> List[int]:
        if len(nums) <= 1:
            return nums
        mid = len(nums) // 2
        left = self.mergeSort(nums[:mid])
        right = self.mergeSort(nums[mid:])
        return self.mergeTwo(left, right)

    def mergeTwo(self, left:List[int], right:List[int]) -> List[int]:
        res = []
        l, r = 0, 0
        while l < len(left) and r < len(right):
            if left[l] <= right[r]:
                res.append(left[l])
                l += 1
            else:
                res.append(right[r])
                r += 1
        if l < len(left):
            res.extend(left[l:])
        if r < len(right):
            res.extend(right[r:])
        return res  
  • 堆排序
    def sortArray(self, nums: List[int]) -> List[int]:
        # 构造大顶堆,交换、取头,再交换、取头
        return self.maxStack(nums)

    def maxStack(self, nums:List[int]) -> List[int]:
        # 构造大顶堆,为长度/2,再减一为序号
        l = len(nums) // 2 - 1
        while l >= 0:
            self.maxCheck(nums, len(nums) - 1, l)
            l -= 1
        l = len(nums) - 1
        while l > 0:     
            # 对序号末尾到1的位置都要交换 
            self.swap(nums, 0, l)  
            l -= 1 
            self.maxCheck(nums, l, 0)
        return nums

    def maxCheck(self, nums:List[int], end:int, idx:int):
        left, right = idx * 2 + 1, idx * 2 + 2
        # 需要比较多次,采用mark记录大值序号的方法
        mark = idx
        if left <= end and nums[left] > nums[mark]:
            mark = left
        if right <= end and nums[right] > nums[mark]:
            mark = right
        if mark != idx:
            self.swap(nums, mark, idx)
            self.maxCheck(nums, end, mark)

对链表进行插入排序:

   
     def insertionSortList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        pre = ListNode()
        pre.next = head
        point = head
        while point.next != None:
            node = point.next
            # 当节点可以在当前位置时
            if point.val <= node.val:
                point = point.next
                continue
            # 需要插入时
            else:
                point.next = node.next
                po = pre
                while po.next != None and po.next.val < node.val:
                    po = po.next
                node.next = po.next
                po.next = node       
        return pre.next

三、动态规划

使用原理:将问题划分为许多个重复的小问题,大问题结果由小问题的结果得到。
使用场景:
  1. 问题为:
    1. 求最大值/最小值
    2. 求是否可行
    3. 求可行个数
  2. 满足不能交换、排序
四要素
  1. 状态
  2. 方程
  3. 初始化
  4. 答案
矩阵类型:
1.二维矩阵树的最短路径    
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        res, l = [], len(triangle)
        if l == 0:
            return 0
        for tri in triangle:
            temp = []
            for t in tri:
                temp.append(t)
            res.append(temp)
        # 获取倒数第二行的坐标
        l -= 2
        # 自底向上计算
        while l >= 0:
            for i in range(0, len(triangle[l])):
                res[l][i] += min(res[l + 1][i], res[l + 1][i + 1])
            l -= 1
        return res[0][0]

(法2:自顶向下)

    def minimumTotal(self, triangle: List[List[int]]) -> int:
        res, l = [], len(triangle)
        if l == 0:
            return 0
        for tri in triangle:
            temp = []
            for t in tri:
                temp.append(t)
            res.append(temp)
        # 自顶向下
        for i in range(1, l):
            t = len(triangle[i])
            for j in range(0, t):
                # 判断该点上一层的左右是否都存在
                if j > 0 and j < len(triangle[i - 1]):
                    res[i][j] += min(res[i - 1][j - 1], res[i - 1][j])
                elif j > 0:
                    res[i][j] += res[i - 1][j - 1]
                else:
                    res[i][j] += res[i - 1][j]
        return min(res[l - 1]) 

2.找出mxn矩阵自左上至右下的最短路径

    
    def minPathSum(self, grid: List[List[int]]) -> int:
        if not grid:
            return 0
        m, n = len(grid), len(grid[0])
        for i in range(0, m):
            for j in range(0, n):
                # 每个点的路径由左或者上的值取最小值决定
                # 判断没点的左或上是否存在点
                if i > 0 and j > 0:
                    grid[i][j] += min(grid[i - 1][j], grid[i][j - 1])
                elif i > 0:
                    grid[i][j] += grid[i - 1][j]
                elif j > 0:
                    grid[i][j] += grid[i][j - 1]
        return grid[m - 1][n - 1]

3.找出mxn中,机器人的可能路线

    def uniquePaths(self, m: int, n: int) -> int:
        dp = []
        for i in range(0, m):
            temp = []
            for j in range(0, n):
                temp.append(1)
            dp.append(temp)
        # 因为机器人往右下路线固定
        # 左侧与上侧的的值都仅有1条路线
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[m - 1][n - 1]

4.找出mxn中,存在障碍物情况下,机器人的可能路线【medium】

     def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        if not obstacleGrid or obstacleGrid[0][0] == 1:
            return 0
        dp, m, n = [], len(obstacleGrid), len(obstacleGrid[0])
        for i in range(0, m):
            temp = []
            for j in range(0, n):
                temp.append(1)
            dp.append(temp)
        i, j = 1, 1
        # 处理[[0,0],[1,1],[0,0]]的数组,设置好左侧与上侧
        while i < m:
            if obstacleGrid[i][0] == 1 or dp[i - 1][0] == 0:
                dp[i][0] = 0
            i += 1
        while j < n:
            if obstacleGrid[0][j] == 1 or dp[0][j - 1] == 0:
                dp[0][j] = 0
            j += 1
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    dp[i][j] = 0
                else:
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[m - 1][n - 1]

序列类型:

1.上楼梯,一次上1级或2级

    def climbStairs(self, n: int) -> int:
        dp = [1, 1]
        for i in range(2, n + 1):
            dp.append(dp[i - 1] + dp[i - 2])
        return dp[n]

2.数组跳步问题

    def canJump(self, nums: List[int]) -> bool:
        # dp[i]表示能否从0跳到i
        if not nums:
            return True
        maxlen = nums[0]
        for i in range(1, len(nums)):
            if i <= maxlen:
                maxlen = max(maxlen, i + nums[i])
        # 保证可到的最大长度大于等于数组长度,且[0]能通过
        if maxlen >= len(nums) - 1:
            return True
        else:
            return False

3.数组跳步升级版(记跳数)   

     def jump(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0
        end, maxidx, step = 0, 0, 0
        # 假设你总是可以到达数组的最后一个位置。
        # 在遍历数组时,不访问最后一个元素,
        # 因为在访问最后一个元素之前,边界一定大于等于最后一个位置
        # 如果访问最后一个元素,在边界正好为最后一个位置的情况下
        # 会增加一次「不必要的跳跃次数」
        for i in range(0, n - 1): 
            # 一直记录最大范围
            maxidx = max(maxidx, i + nums[i])
            # 遇到上次的边界,则更新边界
            if i == end:
                end = maxidx
                step += 1
        return step  

4.最长回文子串问题

    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        # 新赋值数组方法
        dp = [[False] * n for _ in range(n)]
        res = ""
        # 利用串长度的方式展开,以小段得大段
        for l in range(n):
            for i in range(n):
                j = i + l
                if j >= n:
                    break
                if l == 0:
                    dp[i][j] = True
                elif l == 1:
                    dp[i][j] = (s[i] == s[j])
                else:
                    dp[i][j] = (s[i] == s[j] and dp[i + 1][j - 1])
                if dp[i][j] and l + 1 > len(res):
                    res = s[i:j+1]
        return res

5.回文子串分割次数计算【hard

    def minCut(self, s: str) -> int:
        n = len(s)
        # 预处理子串是否回文
        ispali = [[False] * n for _ in range(n)]
        for l in range(n):
            for i in range(n):
                j = i + l
                if j >= n:
                    break
                # 跨度为0,表示1个字符
                if l == 0: 
                    ispali[i][j] = True
                # 跨度为1,表示2个字符
                elif l == 1:
                    ispali[i][j] = (s[i] == s[j])
                else:
                    ispali[i][j] = (s[i] == s[j] and ispali[i + 1][j - 1])
        # 分割次数计算:
        # dp[i]表示从0~i分割次数
        # 1.回文,分割为0;
        # 2.不回文,则j~i回文(0<j<i),得dp[j] + 1.
        dp = [i for i in range(n)]
        for i in range(n):
            if ispali[0][i]:
                dp[i] = 0
            else:
                dp[i] = min([dp[j] + 1 for j in range(i) if ispali[j + 1][i]])
        return dp[n - 1]   

6.找出数组中最长的严格递增序列(可不连续)

    def lengthOfLIS(self, nums: List[int]) -> int:
        # dp[i]表示从0~i最长递增序列的长度
        # dp[i] = max(dp[j]) + 1, if nums[i]>nums[j]
        # 初始值dp[i] = 1
        # 返回值max(dp[i])
        n = len(nums)
        dp = [1 for i in range(n)]
        for i in range(1, n):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

7.判断字符串是否能完全拆分为单词表中的单词

    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        # dp[i] = true 表示从0~i正好能覆盖
        # dp[i] = s[:i+1] in wordDict 
        # 或 dp[i] = dp[j] and s[j+1:i+1] in word Dict
        # 初始值dp[i] = false
        # 返回值dp[n-1]
        n = len(s)
        dp = [False for _ in range(n)]
        for i in range(n):
            if s[:i + 1] in wordDict:
                dp[i] = True
            else:
                for j in range(i):
                    if dp[j] and s[j + 1: i + 1] in wordDict:
                        dp[i] = True
        return dp[n - 1]

双序列类型:

1.最长公共子序列【medium】    

    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        l1, l2 = len(text1), len(text2)
        if l1 == 0 or l2 == 0:
            return 0
        # 发现两个字符串的规律:用二位动态规划数组表示。
        # dp[i][j]表示str2[0~i]与str1[0~j]的公共子序列字符个数
        # dp[i][j] = dp[i-1][j-1] + 1 if str2[i]==str1[j]
        # 或者dp[i][j] = 左侧或上侧的最大数字(等同于str2[i-1]与str1[j]或str2[i]与str1[j-1]的公共子序列个数)
        dp = []
        for i in range(l2 + 1):
            temp = []
            for j in range(l1 + 1):
                temp.append(0)
            dp.append(temp)
        
        for i in range(l2):
            for j in range(l1):
                if text2[i] == text1[j]:
                    dp[i + 1][j + 1] = dp[i][j] + 1
                else:
                    dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j])
        return dp[l2][l1]

2.将字符串a转换为字符串b需要变化的最小次数【hard

    def minDistance(self, word1: str, word2: str) -> int:
        l1, l2 = len(word1), len(word2)
        dp  = [[0] * (l1 + 1) for _ in range(l2 + 1)]
        for i in range(l2 + 1):
            dp[i][0] = i
        for j in range(l1 + 1):
            dp[0][j] = j
        # 动态规划数组:dp[i][j]表示str2[0~i]和str1[0~j]子串内从str1转换成str2的变换次数
        # dp[i][j] = dp[i-1][j-1] if 字符相等,则变换次数不变
        # 或dp[i][j] = 1+上一次最小变换次数min(dp[i][j],dp[i][j-1],dp[i-1][j])
        for i in range(l2):
            for j in range(l1):
                if word2[i] == word1[j]:
                    dp[i + 1][j + 1] = dp[i][j]
                else:
                    dp[i + 1][j + 1] = 1 + min(dp[i][j + 1], dp[i + 1][j], dp[i][j])
        return dp[l2][l1]

3.零钱兑换【medium】  

    def coinChange(self, coins: List[int], amount: int) -> int:
        # dp[i]表示总金额为i的时候,硬币个数        
        # dp[i] = min(dp[i], dp[i-k]+1) if i>=k
        # dp[i]表示最少个数,因此初始值最大为amount+1  
        dp = [(amount + 1) for _ in range(amount + 1)]
        dp[0] = 0
        for i in range(1, amount + 1):
            for c in coins:
                # 判断c硬币是否放入总金额
                if (i - c) >= 0:
                    dp[i] = min(dp[i], dp[i - c] + 1)
        # 设置数额无法达到的默认值-1
        for i in range(amount + 1):
            if dp[i] == amount + 1:
                dp[i] = -1
        return dp[amount]
 

4. 0-1背包问题,求n个物品中选取满足容量m的最大价值

    def backPackII(self, m, A, V):
        # dp[i][j]表示前i个物品,容量是否能为j
        # dp[i][j] = dp[i-1][j] 不装第i个物品的容量
        # 或者 dp[i][j] = dp[i-1][j-a[i]] 装第i个物品
        # 初始值dp[0][0] = 0
        n, dp = len(A), []
        for i in range(n + 1):
            temp = []
            for j in range(m + 1):
                temp.append(0)
            dp.append(temp)
        # i、j表示第i个物品,j+1表示真实容量
        for i in range(n):
            for j in range(m):
                dp[i + 1][j + 1] = dp[i][j + 1]
                if A[i] <= j + 1:
                    dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i][j + 1 - A[i]] + V[i])
        return dp[n][m]    

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值