LeetCode卡片探索-初级算法

卡片探索-初级算法

  • 数组
    • 删除排序数组中的重复项
    • 买卖股票的最佳时机 II
    • 旋转数组
    • 存在重复元素
    • 只出现1次的数字
    • 两个数组的交集2
    • 加一
    • 移动零
    • 两数之和
    • 有效的数独
    • 旋转图像
  • 字符串
    • 反转字符串
    • 整数反转
    • 字符串中的第一个唯一字符
    • 有效的字母异位词
    • 验证回文字符串
    • 字符串转换整数 (atoi)
    • 实现 strStr()
    • 外观数组
    • 最长公共前缀
  • 链表
    • 删除链表中的节点
    • 删除链表的倒数第N个节点
    • 反转链表
    • 合并两个有序链表
    • 回文链表
    • 环形链表
    • 二叉树的最大深度
    • 验证二叉搜索树
    • 对称二叉树
    • 二叉树的层次遍历
    • 将有序数组转换为二叉搜索树
  • 排序和搜索
    • 合并两个有序数组
    • 第一个错误的版本
  • 动态规划
    • 爬楼梯
    • 买卖股票的最佳时机
    • 最大子序和
    • 打家劫舍
  • 设计问题
  • 数学
  • 其他

LeetCode上知识卡片之 初级算法的刷题记录

数组

删除排序数组中的重复项

难点在于原地删除的同时不想时间复杂度太高的话,可以考虑双指针。一个指针遍历数组判断是否为重复元素,一个指针用来搜集不重复元素并把他们安排到数组前列。

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if not nums:
            return 0
        #index为接下来不重复元素的插入位置
        index = 0
        for i in range(1,len(nums)):
            if nums[i] != nums[i-1]:
                index += 1
                nums[index] = nums[i]
        return index + 1

买卖股票的最佳时机 II

1.分析题目要求,最大的利润是在每一次价格的波峰波谷完成买卖。所以统计每一段价格上升的幅度就是答案。
2.利用dp思想,dp[i][0]和dp[i][1]分别记录第i天不持有股票和持有股票的最大收益,则dp[len-1][0]就是要求的输出,又因为每天状态和前一天有关,可以把dp数组压缩为2个变量。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        dp0 = 0
        dp1 = -prices[0]
        for i in range(1,len(prices)):
            dp0,dp1 = max(dp0,dp1+prices[i]),max(dp1,dp0-prices[i])
        return dp0

旋转数组

1.环状替代
2.利用3次反转

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        k %= n
        nums[:] = nums[::-1]
        nums[:k],nums[k:]= nums[:k][::-1],nums[k:][::-1]

存在重复元素

利用hash表达到最优时间效率,python中set类型即可

只出现1次的数字

异或操作可以把出现2次的数字归零,让一个变量等于0然后异或数组中每个数字,剩下的结果就是答案。

两个数组的交集2

1.把一个数组元素存成hash表记录出现次数;遍历另外一个数组元素,如果在hash表出现且次数大于0则加入结果。
2.把两个数组排好序,双指针遍历过去,相等就加入结果,否则根据移动较小元素的指针。

加一

在用数组保存的数字上执行加一操作,用变量carry标志是否进位,从数组尾到头遍历更新数组元素和carry即可。若最后carry为1,说明在数组最右扩招一位。

移动零

双指针,一个用来遍历数组,一个用来记录非0元素应该放入的位置/

两数之和

经典题目,利用hash表即可。

有效的数独

设置9*3个hash表,记录横,竖以及子数独各个数字出现情况。

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        rows = [{} for i in range(9)]
        columns = [{} for i in range(9)]
        boxes = [{} for i in range(9)]
        for i in range(9):
            for j in range(9):
                num = board[i][j]
                if num != '.':
                    num = int(num)
                    box_index = (i // 3 ) * 3 + j // 3
                    rows[i][num] = rows[i].get(num, 0) + 1
                    columns[j][num] = columns[j].get(num, 0) + 1
                    boxes[box_index][num] = boxes[box_index].get(num, 0) + 1
                    if rows[i][num] > 1 or columns[j][num] > 1 or boxes[box_index][num] > 1:
                        return False
        return True

旋转图像

矩阵旋转问题
1.矩阵转置以后,每行反转。
2.分成4个矩形块处理,遍历一个矩形块中每个元素时同时考虑其他3块内对应位置元素互换。

字符串

反转字符串

原地反转字符串,s[i]与s[n-1-i]交换即可

整数反转

整数反转可利用如下代码,但是需要主要python中负数整除往下约等,-123//10 = -13,所以为了方便处理,对x输入取绝对值。

		while x:
            res = 10*res+ x%10
            x //= 10

字符串中的第一个唯一字符

遍历字符串,用hash表存储出现状况即可。但是因为要求是第一次字符:
1.可以再遍历一遍字符串,先出现的hash值为1的就是要求。
2.python的字典是有序字典,直接遍历dict.keys()也可

有效的字母异位词

遍历两个字符串,一个再hash表对应字母数量+1,一个-1,最后如果hash有元素值不为0就是false

验证回文字符串

头尾双指针,往中间遍历,遇到数字字母就检查判断。

字符串转换整数 (atoi)

边界细节比较多,最大最小值判断,符号以及符号位过滤,非法字符。

class Solution:
    def myAtoi(self, str: str) -> int:
        str = str.strip() # 删除首尾空格
        if not str: 
            return 0 # 字符串为空则直接返回
        int_max, int_min = 2 ** 31 - 1, -2 ** 31
        boudry = 2 ** 31 // 10
        res, i, sign = 0, 1, 1
        if str[0] == '-': sign = -1 # 保存负号
        elif str[0] != '+': i = 0 # 若无符号位,则需从 i = 0 开始数字拼接
        for c in str[i:]:
            if not '0' <= c <= '9' : 
                break
            if res > boudry or(res == boudry and c > '7'):
                return int_max if sign == 1 else int_min
            res = 10 * res + ord(c) - ord('0')
        return sign*res

实现 strStr()

暴力匹配或者kmp算法

		nex = [0]*len(needle)
        
        kmp = 0
        for i in range(1,len(needle)):
            while kmp >0 and needle[kmp] != needle[i]:
                kmp = nex[kmp-1]
            if needle[kmp] == needle[i]:
                kmp += 1
            nex[i] = kmp
        
        j = 0
        for i in range(len(haystack)):
            while j >0 and needle[j] != haystack[i]:
                j = nex[j-1]
            if needle[j] == haystack[i]:
                j += 1
            if j == len(needle):
                return i - j + 1
        return -1

外观数组

按层次遍历计数即可

        base  = '1'
        for i in range(n-1):
            num = 1
            s = []
            for j in range(1,len(base)):
                if base[j] == base[j-1]:
                    num += 1
                else:
                    s.append(str(num))
                    s.append(base[j-1])
                    num = 1
            else:
                s.append(str(num))
                s.append((base[len(base)-num]))
            base =''.join(s)
        return base

最长公共前缀

采用水平或者垂直扫描法

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if len(strs) == 0:
            return ''
        s = strs[0]
        for i in range(1, len(strs)):
            while strs[i].find(s) != 0 :
                s = s[:-1]
        return s

链表

删除链表中的节点

一般我们首先想到的思路是找到要删除节点的前一个节点,然后执行p.next = p.next.next把节点从链表上去除。
但是找到前一个节点就要考虑一些边界条件,所以我们可以采用复制的方法

        node.val = node.next.val
        node.next = node.next.next

用下一个节点的信息覆盖掉要删除的节点,达到一样的目的。

删除链表的倒数第N个节点

利用快慢指针,相隔N节点,一次遍历就可以找到要删除的节点位置。

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        if not head or n ==0:
            return None
        new_head = p1 = p2  = ListNode()
        # p2定位到要删除的节点之前。
        new_head.next = head
        for i in range(n):
            p1 = p1.next
            if not p1:
                break
        while p1.next:
            p1 = p1.next
            p2 = p2.next
        p2.next = p2.next.next
        return new_head.next

反转链表

1.正常遍历,利用python多变量赋值语句可以剩下很多工作

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        p1 = None
        p2 = head
        while p2:
            p2.next,p1,p2 = p1,p2,p2.next
        return p1

2.递归,不同于常规问题中递归更好理解,该题的递归模式反而更难理解
我们假设递归的子问题为后续链表已经完成了逆转,那么接下来处理就是怎么把现在处理的节点加入到反转后的链表中。比如下面的 n k n_k nk怎么加入链表,让 n k − > n e x t n_k->next nk>next(就是 n k + 1 n_{k+1} nk+1)指向 n k n_k nk即可。
在这里插入图片描述

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        new_head = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return new_head

合并两个有序链表

类似归并排序处理即可

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        new_head = p = ListNode()
        while l1 and l2:
            if l1.val < l2.val:
                p.next = l1
                l1= l1.next
                p = p.next
            else:
                p.next = l2
                l2= l2.next
                p = p.next     
        #合并剩余部分
        if l1:
            p.next = l1
        else:
            p.next = l2
        return new_head.next

回文链表

反转后半链表,然后逐一比较即可

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        if not head or not head.next:
            return True
        fast = head.next
        slow = head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
        #slow就是前半链表的末尾
        p1 = None
        p2 = slow.next
        while p2:
            p2.next,p1,p2, = p1,p2,p2.next
        #反转链表,p1为反转后的链表头
        while p1 and head:
            if p1.val != head.val:
                return False
            else:
                p1 = p1.next
                head = head.next
        return True
        

环形链表

快慢指针结合python一点小语法

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        fast = slow =head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                break
        else:
            return False
        return True

二叉树的最大深度

树的深度等于左右子树的最大深度再加1,所以很容易处理成递归形式。

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left),self.maxDepth(root.right))+1

验证二叉搜索树

二叉搜索树的性质为根节点大于左子树而小于右子树,我们可以设计一个辅助递归函数设置最大值和最小值,如果节点不在这个范围内就失败,如果符合,则遍历它的左右子树同时更新左右子树值的应在范围。

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        def helper(root,max_value,min_value):
            if not root:
                return True
            elif root.val <= min_value or root.val >= max_value:
                return False
            else:
            #左右子树值随着root.val更新
                return helper(root.left,root.val,min_value) and helper(root.right,max_value,root.val)
        return helper(root,float('inf'),float('-inf'))

对称二叉树

画图对照一下就可以发现,如果一个二叉树的对称的,那么它的镜像反转二叉树要和它相等,既root.left == root2.right and root.right == root2.left,递归很好解决,这里给出迭代代码。

import queue
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        q = queue.Queue()
        q.put(root)
        q.put(root)
        #当作一个对称树
        while q.qsize():
            n1 = q.get()
            n2 = q.get()
            if not n1 and not n2:
                continue
            elif not n1 or not n2 or n1.val != n2.val:
                return False
            else:
                q.put(n1.left)
                q.put(n2.right)
                q.put(n1.right)
                q.put(n2.left)
        return True

二叉树的层次遍历

用队列完成辅助,每次处理一层的节点,并存入返回结果里面。

import queue
class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        q = queue.Queue()
        q.put(root)
        res = []
        if not root:
            return res
        while q.qsize():
            temp = []
            for i in range(q.qsize()):
                x = q.get()
                temp.append(x.val)
                if x.left:
                    q.put(x.left)
                if x.right:
                    q.put(x.right)
            res.append(temp)
        return res

将有序数组转换为二叉搜索树

因为是有序数组,很容易就可以想到一种构建方式,用数组的中间元素做树根,划分数组为左右子数组然后递归构建。(构建方式不止这一种,但是这种省心,左右子树节点个数还相当,比较平衡)

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        def helper(l,r):
            if l > r:
                return None
            mid = l + (r -l)//2
            root = TreeNode(nums[mid])
            root.left = helper(l,mid-1)
            root.right = helper(mid+1,r)
            return root
        return helper(0,len(nums)-1)

排序和搜索

合并两个有序数组

仍然是归并排序的思想,但是因为要原地合并,细节处理上有所不同。需要从尾开始向前填充数字。

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        if not nums2:
            return
        p1 = m - 1
        p2 = n - 1
        p = m + n - 1 
        while p1 >= 0  and p2 >=0:
            if nums1[p1] < nums2[p2]:
                nums1[p] = nums2[p2]
                p2 -= 1
            else:
                nums1[p] = nums1[p1]
                p1 -= 1
            p -= 1
        if p2 >= 0:
            nums1[:p+1] = nums2[:p2+1]

第一个错误的版本

抽象出来的话就是个二分搜索问题。

class Solution:
    def firstBadVersion(self, n):
        """
        :type n: int
        :rtype: int
        """
        l = 1
        r = n
        while l < r:
            mid = l + (r-l)//2
            if isBadVersion(mid):
                r = mid
                #如果r = mid - 1,可能会跳过答案要把while改成l<=r
            else:
                l = mid + 1
        return l

动态规划

爬楼梯

经典dp,当作初始为0的斐波那契数列

class Solution:
    def climbStairs(self, n: int) -> int:
        dp1 = 0
        dp2 = 1
        for i in range(n):
            dp1,dp2 = dp2,dp1+dp2
        return dp2

买卖股票的最佳时机

保存历史最低价,遍历时每次计算当前可得最大收益

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:
            return 0
        
        minp = prices[0]
        res = 0
        for i in range(1,len(prices)):
            res = max(res,prices[i] - minp)
            minp = min(minp,prices[i])
        return res

最大子序和

满足贪心结构,当然写成dp形式也可以

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        out = nums[0]
        temp = nums[0]
        for i in nums[1:]:
            if temp < 0:
                temp = i
            else:
                temp += i
            out = max(out,temp)
        return out

打家劫舍

对于一家屋子有打劫和不打劫两种状态,分别记录一下这两种状态的最大值收益。则每一间屋子的收益最大值仅取决于前一间屋子。

class Solution:
    def rob(self, nums: List[int]) -> int:
        dp1 = dp2 = 0
        for i in range(len(nums)):
            dp1,dp2 = dp2 + nums[i],max(dp1,dp2)
            #如果当前屋子打劫,收益就是前一个屋子不打劫+当前屋子价值
            #如果当前屋子不打劫,收益就是前一个屋子打劫和不打劫的最大值
        return max(dp1,dp2)

设计问题

数学

其他

你可能感兴趣的:(LeetCode,算法,python)