• 常用算法示例


    链表

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

    使用快慢指针,快指针先移动n步。

    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None
    
    class Solution:
        def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
            fast  = head
            slow = head
            i = 1
            # 快指针先移动n步
            while i <= n:
                fast = fast.next
                i += 1
            # 说明要删除的正好是第一个节点
            if fast == None:
                return head.next
            while fast.next:
                slow = slow.next
                fast = fast.next
            # tmp = slow.next
            slow.next = slow.next.next
            return head
    
    
    

    合并两个有序链表

    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) { val = x; }
     * }
     */
    class Solution {
        public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
            if(l1 == null) return l2;
            if(l2 == null) return l1;
            if(l1.val < l2.val){
                l1.next = mergeTwoLists(l1.next, l2);
                return l1;
            }else{
                l2.next = mergeTwoLists(l1, l2.next);
                return l2;
            }
    
        }
    }
    
    

    上述是使用递归的解法,应该还有更好的方法。非递归版本:

    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) { val = x; }
     * }
     */
    class Solution {
        public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
            ListNode dummyHead = new ListNode(-1);
            ListNode cur = dummyHead;
            while(l1 != null && l2 != null){
                if(l1.val<l2.val){
                    cur.next = l1;
                    l1 = l1.next;
                    cur = cur.next;
                }
                else{
                    cur.next = l2;
                    l2 = l2.next;
                    cur = cur.next;
                }
            }
            if(l1 == null){
                cur.next = l2;
            }
            else{
                cur.next = l1;
            }
            return dummyHead.next;
        }
    }
    

    非递归解法好像跟容易理解一些。python实现:

    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    class Solution:
        def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
            new_head = ListNode(-1, None)
            cur = new_head
            while l1 != None and l2 != None:
                if l1.val < l2.val:
                    cur.next = l1
                    l1 = l1.next
                    cur = cur.next
                else:
                    cur.next = l2
                    l2 = l2.next
                    cur = cur.next
            # 处理剩余的
            if l1 == None:
                cur.next = l2
            else:
                cur.next = l1
            return new_head.next
    
    

    环形链表

    判断链表中是否有环。典型的快慢指针题目。

    /**
     * Definition for singly-linked list.
     * class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) {
     *         val = x;
     *         next = null;
     *     }
     * }
     */
    public class Solution {
        public boolean hasCycle(ListNode head) {
            if (head == null || head.next == null)
                return false;
            ListNode slow = head;
            ListNode fast = head.next;
            while(fast != slow){
                if (fast == null || fast.next == null){
                    return false;
                }
                slow = slow.next;
                fast = fast.next.next;
            }
            return true;
        }
    }
    

    python实现:

    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None
    
    class Solution:
        def hasCycle(self, head: ListNode) -> bool:
            if head is None:
                return False
            fast = head.next
            while fast != None and head != None and fast.next != None:
                if fast == head:
                    return True
                fast = fast.next.next
                head = head.next
            return False
        
    

    环形链表②

    给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

    为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

    说明:不允许修改给定的链表。

    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None
    
    class Solution:
        def detectCycle(self, head: ListNode) -> ListNode:
            fast = head
            slow = head
            while True:
                # 两个都为空时直接返回,说明没有环
                if not (fast and fast.next):
                    return
                fast = fast.next.next
                slow = slow.next
                if fast == slow:
                    # 说明相遇了
                    break
            fast = head
            # 第二次相遇时fast指向的节点就是环的入口节点
            while fast != slow:
                fast = fast.next
                slow = slow.next
    
            return fast
    

    动态规划

    爬楼梯

    状态转移方程:dp[n] =dp[n-1] + dp[n-2]

    上1阶台阶:有1种方式

    上2阶台阶:有1+1和2两种方式

    上n阶台阶:到达第n阶的方法总数就是到第n-1阶和第n-2阶的方法数之和。

    func climbStairs(n int) int {
        if n ==1 {
            return 1
        }
        dp := make([]int, n+1)
        dp[1] = 1
        dp[2] = 2
        for i:=3; i<=n; i++ {
            dp[i] = dp[i-1] + dp[i-2]
        }
        return dp[n]
    }
    

    最大子序和

    首先我们分析题目,一个连续子数组一定要以一个数作为结尾,那么我们可以将状态定义成如下:

    dp[i]:表示以 nums[i] 结尾的连续子数组的最大和。
    

    根据状态的定义,我们继续进行分析:如果要得到 dp[i],那么 nums[i] 一定会被选取。并且 dp[i] 所表示的连续子序列与 dp[i-1] 所表示的连续子序列很可能就差一个 nums[i] 。即:

    dp[i] = dp[i-1]+nums[i] , if (dp[i-1] >= 0)
    

    但是这里我们遇到一个问题,很有可能 dp[i-1] 本身是一个负数。那这种情况的话,如果 dp[i] 通过 dp[i-1]+nums[i] 来推导,那么结果其实反而变小了,因为我们 dp[i] 要求的是最大和。所以在这种情况下,如果 dp[i-1] < 0,那么 dp[i] 其实就是 nums[i] 的值。即

    dp[i] = nums[i] , if (dp[i-1] < 0)
    

    综上分析,我们可以得到:

    dp[i]=max(nums[i], dp[i−1]+nums[i])
    

    得到了状态转移方程,但是我们还需要通过一个已有的状态的进行推导,我们可以想到 dp[0] 一定是以 nums[0] 进行结尾,所以

    dp[i] = dp[i-1]+nums[i] , if (dp[i-1] >= 0)
    dp[0] = nums[0]
    

    在很多题目中,因为 dp[i] 本身就定义成了题目中的问题,所以 dp[i] 最终就是要的答案。但是这里状态中的定义,并不是题目中要的问题,不能直接返回最后的一个状态 (这一步经常有初学者会摔跟头)。所以最终的答案,其实我们是寻找:

    max(dp[0], dp[1], ..., d[i-1], dp[i])
    

    python实现:

    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            # 创建一个dp数组
            n = len(nums)
            dp = [0]*n
            dp[0] = nums[0]
            for i in range(1, n):
                dp[i] = max(nums[i], dp[i-1]+nums[i])
            return max(dp)
    

    不得不说,这是一道经典的动态规划题目,必须要掌握。

    最长上升子序列

    给定一个无序的整数数组,找到其中最长上升子序列的长度。

    示例:

    输入: [10,9,2,5,3,7,101,18]
    输出: 4 
    解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
    

    说明:

    • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。

    因为题目中没有要求连续,所以LIS可能是连续的,也可能是非连续的。同时,LIS符合可以从其子问题的最优解来进行构建的条件。所以我们可以尝试用动态规划来进行求解。首先我们定义状态:

    dp[i] :表示以nums[i]结尾的最长上升子序列的长度
    

    分析:

    • 如果nums[i]比前面的所有元素都小,那么dp[i]等于1(即它本身)
    • 如果nums[i]的前面存在比他小的元素nums[j],那么dp[i]就等于dp[j]+1 (该结论错误,比如nums[3]>nums[0],即9>1,但是dp[3]并不等于dp[0]+1)

    我们先初步得出上面的结论,但是我们发现了一些问题。因为dp[i]前面比他小的元素,不一定只有一个!

    可能除了 nums[j],还包括 nums[k],nums[p] 等等等等

    dp[i] = max(dp[j]+1,dp[k]+1,dp[p]+1,.....)
    只要满足:
    	nums[i] > nums[j]
    	nums[i] > nums[k]
    	nums[i] > nums[p]
    	...
    

    golang实现:

    func lengthOfLIS(nums []int) int {
    	if len(nums) < 1 {
    		return 0
    	}
    	dp := make([]int, len(nums))
    	result := 1
    	for i := 0; i < len(nums); i++ {
    		dp[i] = 1
    		for j := 0; j < i; j++ {
    			if nums[j] < nums[i] {
    				dp[i] = max(dp[j]+1, dp[i])
    			}
    		}
    		result = max(result, dp[i])
    	}
    	return result
    }
    
    func max(a, b int) int {
    	if a > b {
    		return a
    	}
    	return b
    }
    

    python实现:

    class Solution:
        def lengthOfLIS(self, nums: List[int]) -> int:
            n = len(nums)
            if n<1:
                return 0
            dp  = [0] * n
            result = 1
            for i in range(n):
                dp[i] = 1
                for j in range(i):
                    if nums[j] < nums[i]:
                        dp[i] = max(dp[j]+1, dp[i])
                result = max(result, dp[i])
            return result
    

    三角形最小路径和

    给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

    相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。

    题解:

    其实也就等同于,每一步我们只能往下移动一格或者往右移动一格。

    题目很明显就是一个找最优解的问题,并且可以从子问题的最优解进行构建。所以我们通过动态规划进行求解。首先,我们定义状态:

    dp[i][j]:表示包含第i行第j列元素的最小路径和
    

    我们很容易想到可以自顶向下进行分析。并且,无论最后的路径是哪一条,它一定要经过最顶上的元素,即 [0,0]。所以我们需要对 dp[0][0] 进行初始化。

    dp[0][0] = [0][0]位置所在的元素值
    

    继续分析,如果我们要求dp[i][j],那么气其一定来自于自己头顶上的两个元素移动而来的,它们分别是dp[i-1][j-1]dp[i-1][j]。所以状态转移方程为:

    dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
    

    golang的实现:

    func minimumTotal(triangle [][]int) int {
        if len(triangle) < 1 {
            return 0
        }
        if len(triangle) == 1 {
            return triangle[0][0]
        }
    	dp := make([][]int, len(triangle))
    	for i, arr := range triangle {
    		dp[i] = make([]int, len(arr))
    	}
        result := 1<<31 - 1
    	dp[0][0] = triangle[0][0]
    	dp[1][1] = triangle[1][1] + triangle[0][0]
    	dp[1][0] = triangle[1][0] + triangle[0][0]
    
    	for i := 2; i < len(triangle); i++ {
    		for j := 0; j < len(triangle[i]); j++ {
    			if j == 0 {
    				dp[i][j] = dp[i-1][j] + triangle[i][j]
    			} else if j == (len(triangle[i]) - 1) {
    				dp[i][j] = dp[i-1][j-1] + triangle[i][j]
    			} else {
    				dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
    			}
    		}  
    	}
        for _,k := range dp[len(dp)-1] {
            result = min(result, k)
        }
    	return result
    }
    
    func min(a, b int) int {
    	if a > b {
    		return b
    	}
    	return a
    }
    

    python的实现:

    class Solution:
        def minimumTotal(self, triangle: List[List[int]]) -> int:
            n = len(triangle)
            if n < 1:
                return 0
            if n == 1:
                return triangle[0][0]
            
            dp = [0] * n
            for i in range(n):
                dp[i] = [0] * len(triangle[i])
            
            result = 10**5
            dp[0][0] = triangle[0][0]
            dp[1][1] = triangle[1][1] + triangle[0][0]
            dp[1][0] = triangle[1][0] + triangle[0][0]
    
            for i in range(2, n):
                for j in range(len(triangle[i])):
                    if j == 0:
                        dp[i][j] = dp[i-1][j] + triangle[i][j]
                    elif j == len(triangle[i]) - 1:
                        dp[i][j] = dp[i-1][j-1] + triangle[i][j]
                    else:
                        dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
            
            result = min(dp[-1])
            return result
    

    最小路径和

    给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

    说明:每次只能向下或者向右移动一步。

    示例:

    输入:
    [
      [1,3,1],
      [1,5,1],
      [4,2,1]
    ]
    输出: 7
    解释: 因为路径 1→3→1→1→1 的总和最小。
    

    题解:题目明显符合可以从子问题的最优解进行构建,所以我们考虑使用动态规划进行求解。首先我们定义状态:

    dp[i][j]:表示包含第i行j列元素的最小路径和
    

    同样,因为任何一条到达右下角的路径,都会经过[0][0]这个元素,所以我们需要对dp[0][0]进行初始化。

    dp[0][0] = [0][0]位置所在的元素值
    

    继续分析,根据题目给的条件,如果我们要求 dp[i][j] ,那么它一定是从自己的上方或者左边移动而来。

    进而,我们得到状态转移方程:

    dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
    

    同样,我们需要考虑两种特殊情况:

    • 最上面一行,只能由左边移动而来(1-3-1)
    • 最左边一行,只能由上面移动而来(1-1-4)

    最后,因为我们的目标是从左上角走到右下角,整个网格的最小路径和其实就是包含右下角元素的最小路径和

    设:dp的长度为L
    最终结果就是:dp[L-1][len(L-1)-1]
    

    综上,我们就分析完了,我们总共进行了4步:

    1. 定义状态
    2. 总结状态转移方程
    3. 分析状态转移方程不能满足的特殊情况
    4. 得到最终解

    go实现:

    func minPathSum(grid [][]int) int {
    	l := len(grid)
    	if l < 1 {
    		return 0
    	}
    	dp := make([][]int, l)
    	for i, arr := range grid {
    		dp[i] = make([]int, len(arr))
    	}
    	dp[0][0] = grid[0][0]
    	for i := 0; i < l; i++ {
    		for j := 0; j < len(grid[i]); j++ {
    			if i == 0 && j != 0 {
    				dp[i][j] = dp[i][j-1] + grid[i][j]
    			} else if j == 0 && i != 0 {
    				dp[i][j] = dp[i-1][j] + grid[i][j]
    			} else if i !=0 && j != 0 {
    				dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
    			}
    		}
    	}
    	return dp[l-1][len(dp[l-1])-1]
    }
    
    func min(a, b int) int {
    	if a > b {
    		return b
    	}
    	return a
    }
    

    python实现:

    class Solution:
        def minPathSum(self, grid: List[List[int]]) -> int:
            # m行 * n列
            m = len(grid)
            n = len(grid[0])
            # 初始化dp
            dp = [0] * m
            for i in range(m):
                dp[i] = [0] * n
            
            
            for i in range(m):
                for j in range(n):
                    if i==0 and j == 0:
                        dp[i][j] = grid[0][0]
                    elif i == 0 and j > 0:
                        dp[i][j] = dp[i][j-1] + grid[i][j]
                    elif i > 0 and j == 0:
                        dp[i][j] = dp[i-1][j] + grid[i][j]
                    else:
                        dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
            
            return dp[m-1][n-1]
    

    打家劫舍

    你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

    给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

    示例1:

    输入: [1,2,3,1]
    输出: 4
    解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
    	 偷窃到的最高金额 = 1 + 3 = 4 。
    

    示例2:

    输入: [2,7,9,3,1]
    输出: 12
    解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
         偷窃到的最高金额 = 2 + 9 + 1 = 12 。
    

    题解:

    如何定义dp[i]?

    可以有两种定义:

    1. dp[i]: 偷盗 第i个房子时,所获取的最大利益
    2. dp[i]: 偷盗 第i个房子时,所获取的最大利益

    如果我们定义为状态一,因为我们没办法知道获取最高金额时,小偷到底偷盗了哪些房屋。所以我们需要找到所有状态中的最大值,才能找到我们的最终答案。即:

    max(dp[0],dp[1],.....,dp[len(dp)-1])
    

    如果我们定义为状态二,因为小偷一定会从前偷到最后(强调:偷盗至第i个房间,不代表小偷要从第i个房间中获取财物)。所以我们的最终答案很容易确定。即:

    dp[i]
    

    如果是状态一,偷盗含第 i 个房间时能获取的最高金额,我们相当于要找到偷盗每一间房子时可以获取到的最大金额。比如下图,我们要找到 dp[4] ,也就是偷盗 9 这间房子时,能获取到的最大金额。那我们就需要找到与9不相邻的前后两段中能获取到的最大金额。我们发现题目进入恶性循环,因为我们若要找到与9不相邻的两端中能偷盗的最大金额,根据 dp[i] 的定义,我们就又需要分析在这两段中盗取每一间房子时所能获取的最大利益!想想都很可怕!所以我们放弃掉这种状态的定义。

    如果是状态二,偷盗至第 i 个房子时,所能获取的最大利益。那我们可以想到,由于不可以在相邻的房屋闯入,所以 至i房屋可盗窃的最大值,要么就是至 i-1 房屋可盗窃的最大值,要么就是至 i-2 房屋可盗窃的最大值加上当前房屋的值,二者之间取最大值,即:

    dp[i] = max(dp[i-2]+nums[i], dp[i-1])
    

    python实现:

    class Solution:
        def rob(self, nums: List[int]) -> int:
            if len(nums) < 1:
                return 0
            if len(nums) == 1:
                return nums[0]
            if len(nums) == 2:
                return max(nums[0], nums[1])
            
            dp = [0] * len(nums)
            dp[0] = nums[0]
            dp[1] = max(nums[0], nums[1])
            for i in range(2, len(nums)):
                dp[i] = max(dp[i-2] + nums[i], dp[i-1])
            
            return dp[-1]
    
    

    python实现的时候一定要注意边界条件!

    java实现:

    class Solution {
        //找到动态规划的算式
        // dp[i] = max(dp[i-2]+nums[i], dp[i-1])
    
        public int rob(int[] nums) {
            int prevMax = 0;
            int currMax = 0;
            for (int x : nums) {
                int temp = currMax;
                currMax = Math.max(prevMax + x, currMax);
                prevMax = temp;
            }
        return currMax;
        }
    }
    

    go实现:

    func rob(nums []int) int {
        if len(nums) < 1 {
            return 0
        }
        if len(nums) == 1 {
            return nums[0]
        }
        if len(nums) == 2 {
            return max(nums[0],nums[1])
        }
        dp := make([]int, len(nums))
        dp[0] = nums[0]
        dp[1] = max(nums[0],nums[1])
        for i := 2; i < len(nums); i++ {
            dp[i] = max(dp[i-2]+nums[i],dp[i-1])
        }
        return dp[len(dp)-1]
    }
    
    func max(a,b int) int {
        if a > b {
            return a
        }
        return b
    }
    

    字符串

    反转字符串

    编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

    比较简单,原地操作。

    class Solution:
        def reverseString(self, s):
            """
            :type s: List[str]
            :rtype: void Do not return anything, modify s in-place instead.
            """
            i,j = 0, len(s)-1
            while i<j:
                s[i],s[j]=s[j],s[i]
                i+=1
                j-=1
    

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

    给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1 。

    示例:

    s = "leetcode"
    返回 0.
    
    s = "loveleetcode",
    返回 2.  
    

    题目不难,直接进行分析。由于字母共有 26 个,所以我们可以声明一个 26 个长度的数组(该种方法在本类题型很常用)因为字符串中字母可能是重复的,所以我们可以先进行第一次遍历,在数组中记录每个字母的最后一次出现的所在索引。然后再通过一次循环,比较各个字母第一次出现的索引是否为最后一次的索引。如果是,我们就找到了我们的目标,如果不是我们将其设为 -1(标示该元素非目标元素)如果第二次遍历最终没有找到目标,直接返回 -1即可。

    python实现:

    class Solution:
        def firstUniqChar(self, s: str) -> int:
            data_dict = {}
            for index, v in enumerate(s):
                data_dict[v] = index
            
            for i, v in enumerate(s):
                if i == data_dict[v]:
                    return i
                else:
                    data_dict[v] = -1
            return -1
    
    

    go实现:

    func firstUniqChar(s string) int {
        var arr [26]int
        for i,k := range s {
            arr[k - 'a'] = i
        }
        for i,k := range s {
            if i == arr[k - 'a']{
                return i
            }else{
                arr[k - 'a'] = -1
            }
        }
        return -1
    }
    

    二叉树

    二叉树的最大深度

    python实现:

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def maxDepth(self, root: TreeNode) -> int:
            if not root:
                return 0
            return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
    

    go实现:

    func maxDepth(root *TreeNode) int {
        if root == nil {
            return 0
        }
        return max(maxDepth(root.Left), maxDepth(root.Right)) + 1
    }
    
    func max(a int, b int) int {
        if a > b {
            return a
        }
        return b
    }
    

    二叉树的层序遍历

    给你一个二叉树,请你返回其按层序遍历得到的节点值。(即逐层地,从左到右访问所有节点)

    我们先考虑本题的递归解法。想到递归,我们一般先想到DFS。我们可以对该二叉树进行先序遍历(根左右的顺序),同时,记录节点所在的层次level,并且每一层都定义一个数组,然后将访问到的节点值放入对应层的数组中。

    go实现:

    func levelOrder(root *TreeNode) [][]int {
        return dfs(root, 0, [][]int{})
    }
    
    func dfs(root *TreeNode, level int, res [][]int) [][]int {
    	if root == nil {
    		return res
    	}
    	if len(res) == level {
            // 需要新加一层slice
    		res = append(res, []int{root.Val})
    	} else {
    		res[level] = append(res[level], root.Val)
    	}
    	res = dfs(root.Left, level+1, res)
    	res = dfs(root.Right, level+1, res)
        return res
    }
    

    python实现:

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def levelOrder(self, root: TreeNode) -> List[List[int]]:
            levels = []
            if not root:
                return levels
    
            def helper(node, level):
                # start the current level
                if len(levels) == level:
                    levels.append([])
    
                # append the current node value
                levels[level].append(node.val)
    
                # process child nodes for the next level
                if node.left:
                    helper(node.left, level+1)
                if node.right:
                    helper(node.right, level+1)
    
            helper(root, 0)
            return levels
    

    验证二叉搜索树

    给定一个二叉树,判断其是否是一个有效的二叉搜索树BST。

    假设一个二叉搜索树具有如下特征:

    • 节点的左子树只包含小于当前节点的数。
    • 节点的右子树只包含大于当前节点的数。
    • 所有左子树和右子树自身必须也是二叉搜索树。

    思路:离不开递归思想。首先看完题目,我们很容易想到 遍历整棵树,比较所有节点,通过 左节点值<节点值,右节点值>节点值 的方式来进行求解。但是这种解法是错误的,因为对于任意一个节点,我们不光需要左节点值小于该节点,并且左子树上的所有节点值都需要小于该节点。(右节点一致)所以我们在此引入上界与下界,用以保存之前的节点中出现的最大值与最小值

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def isValidBST(self, root: TreeNode) -> bool:
            if not root:
                return True
            return self.isBST(root, -2**32, 2**32)
        
        def isBST(self, root: TreeNode, min: int, max: int) -> bool:
            if root is None:
                return True
            if min >=root.val or max <=root.val:
                return False
            return self.isBST(root.left, min, root.val) and self.isBST(root.right, root.val, max)
    
    

    go实现:

    func isValidBST(root *TreeNode) bool {
        if root == nil{
            return true
        }
        return isBST(root,math.MinInt64,math.MaxInt64)
    }
    
    func isBST(root *TreeNode,min, max int) bool{
        if root == nil{
            return true
        }
        if min >= root.Val || max <= root.Val{
            return false
        }
        // 左子树root.Val应该是最大值,右子树root.Val应该是最小值
        return isBST(root.Left,min,root.Val) && isBST(root.Right,root.Val,max)
    }
    

    二叉搜索树中的搜索

    给定二叉搜索树(BST)的根节点和一个值。你需要在BST中找到节点值等于给定值的节点。返回以该节点为根的子树。如果节点不存在,则返回NULL。

    比较简单,就是二叉搜素树的应用。

    python实现:

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def searchBST(self, root: TreeNode, val: int) -> TreeNode:
            if not root:
                return None
            if  val == root.val:
                return root
            elif val < root.val:
                return self.searchBST(root.left, val)
            else:
                return self.searchBST(root.right, val)
    
    

    go实现:

    /**
     * Definition for a binary tree node.
     * type TreeNode struct {
     *     Val int
     *     Left *TreeNode
     *     Right *TreeNode
     * }
     */
    func searchBST(root *TreeNode, val int) *TreeNode {
        if root == nil {
            return root
        }
        if val == root.Val {
            return root
        }else if val < root.Val {
            return searchBST(root.Left, val)
        }else {
            return searchBST(root.Right, val)
        }
    }
    
    
    

    java迭代实现:

    //迭代
    public TreeNode searchBST(TreeNode root, int val) {
            while (root != null) {
                if (root.val == val) {
                    return root;
                } else if (root.val > val) {
                    root = root.left;
                } else {
                    root = root.right;
                }
            }
            return null;
     }
    

    迭代与递归的区别

    递归:重复调用函数自身实现循环称为递归;

    迭代:利用变量的原值推出新值称为迭代,或者说迭代是函数内某段代码实现循环;

    删除二叉搜索树中的节点

    给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

    一般来说,删除节点可分为两个步骤:

    首先找到需要删除的节点;
    如果找到了,删除它。
    说明: 要求算法时间复杂度为 O(h),h 为树的高度。

    题解:

    首先找到该删除节点,找到之后会出现三种情况:

    1. 待删除节点的左子树为空,让待删除节点的右子树替代自己。
    2. 待删除节点的右子树为空,让待删除节点的左子树替代自己。
    3. 如果待删除的节点的左右子树都不为空。我们需要找到比当前节点小的最大节点(前驱),来替换自己,或者比当前节点大的最小节点(后继)来替换自己。

    go语言使用后继节点来实现:

    func deleteNode(root *TreeNode, key int) *TreeNode {
        if root == nil {
            return nil
        }
        if key < root.Val {
            root.Left = deleteNode( root.Left, key )
            return root
        }
        if key > root.Val {
            root.Right = deleteNode( root.Right, key )
            return root
        }
        //到这里意味已经查找到目标
        if root.Right == nil {
            //右子树为空
            return root.Left
        }
        if root.Left == nil {
            //左子树为空
            return root.Right
        }
        // 先取当前节点的右节点,然后一直取该节点的左节点,即比当前节点大的最小节点
        minNode := root.Right
        for minNode.Left != nil {
            minNode = minNode.Left
        }
        root.Val = minNode.Val
        root.Right = deleteMinNode( root.Right )
        return root
    }
    
    
    func deleteMinNode( root *TreeNode ) *TreeNode {
        if root.Left == nil {
            pRight := root.Right
            root.Right = nil
            return pRight
        }
        root.Left = deleteMinNode( root.Left )
        return root
    }
    

    python实现:

    class Solution:
        def successor(self, root):
            """
            One step right and then always left
            """
            root = root.right
            while root.left:
                root = root.left
            return root.val
        
        def predecessor(self, root):
            """
            One step left and then always right
            """
            root = root.left
            while root.right:
                root = root.right
            return root.val
            
        def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
            if not root:
                return None
            
            # delete from the right subtree
            if key > root.val:
                root.right = self.deleteNode(root.right, key)
            # delete from the left subtree
            elif key < root.val:
                root.left = self.deleteNode(root.left, key)
            # delete the current node
            else:
                # the node is a leaf
                if not (root.left or root.right):
                    root = None
                # the node is not a leaf and has a right child
                elif root.right:
                    root.val = self.successor(root)
                    root.right = self.deleteNode(root.right, root.val)
                # the node is not a leaf, has no right child, and has a left child    
                else:
                    root.val = self.predecessor(root)
                    root.left = self.deleteNode(root.left, root.val)
                            
            return root
    
    

    平衡二叉树

    给定一个二叉树,判断它是否是高度平衡的二叉树。

    利用全局变量的一种解法:

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        private boolean result = true;
        public boolean isBalanced(TreeNode root) {
            maxDepth(root);
            return result;
        }
        public int maxDepth(TreeNode root){
            if(root == null) return 0;
            int left_deep = maxDepth(root.left);
            int right_deep = maxDepth(root.right);
            if (Math.abs(left_deep - right_deep) > 1) result = false;
            return Math.max(left_deep, right_deep) + 1;
        }
    }
    

    另一种解法:

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        public boolean isBalanced(TreeNode root) {
            if (root == null) return true;
            if (!isBalanced(root.left)) return false;
            if(!isBalanced(root.right)) return false;
            
            int leftH = dfs(root.left) + 1;
            int rightH = dfs(root.right) + 1;
            if(Math.abs(leftH - rightH)>1){
                return false;
            }
            else{
                return true;
            }
    
        }
        //计算二叉树的深度
        public int dfs(TreeNode x){
            if(x == null) return 0;
            if(x.left == null && x.right == null) return 1;
            int leftH = dfs(x.left) + 1;
            int rightH = dfs(x.right) + 1;
            return Math.max(leftH, rightH);
        }
    }
    

    python解法:

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def isBalanced(self, root: TreeNode) -> bool:
            if not root:
                return True
            if not self.isBalanced(root.left) or not self.isBalanced(root.right):
                return False
            
            l_depth  = self.dfs(root.left) + 1
            r_depth = self.dfs(root.right) + 1
            if abs(l_depth-r_depth) > 1:
                return False
            return True
    	
        # 计算出二叉树的高度
        def dfs(self, root: TreeNode):
            if not root:
                return 0
            if root.left is None and root.right is None:
                return 1
            l_depth = self.dfs(root.left) + 1
            r_depth = self.dfs(root.right) + 1
            return max(l_depth, r_depth)
    
    

    完全二叉树

    给出一格完全二叉树,求出该树的节点个数。

    说明:

    完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h 个节点。

    可以直接通过递归求出二叉树的个数:

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def countNodes(self, root: TreeNode) -> int:
            if root:
                return 1 + self.countNodes(root.left) + self.countNodes(root.right)
            else:
                return 0
            
    

    但是上述解法对所有二叉树都有效,没有充分利用完全二叉树的特性来简化计算。

    由于题中已经告诉我们这是一颗完全二叉树,我们又已知了完全二叉树除了最后一层,其他层都是满的,并且最后一层的节点全部靠向了左边。那我们可以想到,可以将该完全二叉树可以分割成若干满二叉树和完全二叉树满二叉树直接根据层高h计算出节点为2^h-1,*然后*继续计算子树中完全二叉树节点。那如何分割成若干满二叉树和完全二叉树呢?对任意一个子树,遍历其左子树层高left,右子树层高right,相等左子树则是满二叉树,否则右子树是满二叉树

    我们看到根节点的左右子树高度都为3,那么说明左子树是一颗满二叉树。因为节点已经填充到右子树了,左子树必定已经填满了。所以左子树的节点总数我们可以直接得到,是2^left - 1,加上当前这个root节点,则正好是2^3,即 8。然后只需要再对右子树进行递归统计即可。

    我们看到左子树高度为3,右子树高度为2。说明此时最后一层不满,但倒数第二层已经满了,可以直接得到右子树的节点个数。同理,右子树节点+root节点,总数为2right,即22。再对左子树进行递归查找。

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        public int countNodes(TreeNode root) {
            if (root == null) {
                return 0;
            }
            int left = countLevel(root.left);
            int right = countLevel(root.right);
            if (left == right) {
                return countNodes(root.right) + (1 << left);
            } else {
                return countNodes(root.left) + (1 << right);
            }
        }
    
        //计算完全二叉树的高度
        private int countLevel(TreeNode root) {
            int level = 0;
            while (root != null) {
                level++;
                root = root.left;
            }
            return level;
        }
    }
    

    go实现:

    /**
     * Definition for a binary tree node.
     * type TreeNode struct {
     *     Val int
     *     Left *TreeNode
     *     Right *TreeNode
     * }
     */
    func countNodes(root *TreeNode) int {
        if root == nil {
            return 0
        }
        left := countLevel(root.Left)
        right := countLevel(root.Right)
        if left == right {
            return countNodes(root.Right) + (1<<left)
        } else {
            return countNodes(root.Left) + (1<<right)
        }
    }
    
    func countLevel(root *TreeNode) int {
        level := 0
        for root != nil {
            level++
            root = root.Left
        }
        return level
    }
    

    二叉树的剪枝

    给定二叉树根结点 root ,此外树的每个结点的值要么是 0,要么是 1。

    返回移除了所有不包含 1 的子树的原二叉树。

    ( 节点 X 的子树为 X 本身,以及所有 X 的后代。)

    假设有一棵树,最上层的是root节点,而父节点会依赖子节点。如果现在有一些节点已经标记为无效,我们要删除这些无效节点。如果无效节点的依赖的节点还有效,那么不应该删除,如果无效节点和它的子节点都无效,则可以删除。剪掉这些节点的过程,称为剪枝,目的是用来处理二叉树模型中的依赖问题

    剪什么大家应该都能理解。那关键是怎么剪?过程也很简单,在递归的过程中,如果当前结点的左右节点皆为空,且当前结点为0,我们就将当前节点剪掉即可。

    go语言实现:

    /**
     * Definition for a binary tree node.
     * type TreeNode struct {
     *     Val int
     *     Left *TreeNode
     *     Right *TreeNode
     * }
     */
    
    func pruneTree(root *TreeNode) *TreeNode {
    	if root == nil {
    		return nil
    	}
    	root.Left = pruneTree(root.Left)
    	root.Right = pruneTree(root.Right)
    	if root.Left == nil && root.Right == nil && root.Val == 0 {
    		return nil
    	}
    	return root
    }
    

    数学

    给定一个没有重复数字的序列,返回其所有可能的全排列。

    示例:

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

    思路:是回溯算法。https://leetcode-cn.com/problems/permutations/

    滑动窗口

    滑动窗口最大值

    给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。

    暴力解法:时间复杂度O(LK)。

    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            int len = nums.length;
            if (len * k == 0) return new int[0];
            int [] win = new int[len - k + 1];
            //遍历所有的滑动窗口
            for (int i = 0; i < len - k + 1; i++) {
                int max = Integer.MIN_VALUE;
                //找到每一个滑动窗口的最大值
                for(int j = i; j < i + k; j++) {
                    max = Math.max(max, nums[j]);
                }
                win[i] = max;
            }
            return win;
        }
    }
    

    双向队列解法:O(N)

    我们可以使用双向队列,该数据结构可以从两端以常数时间压入/弹出元素。

    存储双向队列的索引比存储元素更方便,因为两者都能在数组解析中使用。

    python实现:

    from collections import deque
    
    
    class Solution:
        def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
            n = len(nums)
            if n * k == 0:
                return []
            if k == 1:
                return nums
    
            # i是指元素的下标
            def clean_deque(i):
                # deq[0]就是下标0,这里如果i==k时
                if deq and deq[0] == i - k:
                    deq.popleft()
                # 循环移除队列中小于当前nums[i]的值
                while deq and nums[i] > nums[deq[-1]]:
                    deq.pop()
    
            deq = deque()
            max_idx = 0  # 标记最大值的位置
            # 队列的初始化
            for i in range(k):
                clean_deque(i)
                deq.append(i)
                if nums[i] > nums[max_idx]:
                    max_idx = i
            output = [nums[max_idx]]
    
            for i in range(k, n):
                clean_deque(i)
                deq.append(i)
                output.append(nums[deq[0]])
            return output
    
    

    无重复字符的最长子串

    给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

    思路:建立一个滑动窗口,记录下串口出现过的最大值即可。我们唯一要做的是 --- 尽可能扩大窗口。当一个新元素与原来窗口中的元素一样时,我们缩小窗口,将出现过的元素以及其左边的元素统统移除。

    python实现:

    class Solution:
        def lengthOfLongestSubstring(self, s: str) -> int:
            if s == '':
                return 0
            if len(s) == 1:
                return 1
    
            # lookup为窗口内的字符串,这个窗口要设计好
            # 遍历的时候主要就是更新这个窗口
            lookup = list()
            n = len(s)
            max_len = 0
            cur_len = 0
            for i in range(n):
                val = s[i]
                if val not in lookup:
                    lookup.append(val)
                    cur_len+=1
                else:
                    index = lookup.index(val)
                    lookup = lookup[index+1:] # 移出出现过的元素及其左边
                    lookup.append(val)
                    cur_len = len(lookup)
                if cur_len > max_len:
                    max_len = cur_len
            return max_len
    

    找到字符串中所有字母异位词

    给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

    字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

    说明:

    • 字母异位词指字母相同,但排列不同的字符串。
    • 不考虑答案输出的顺序。
    输入:
    s: "cbaebabacd" p: "abc"
    输出:
    [0, 6]
    解释:
    起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
    起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
    
    输入:
    s: "abab" p: "ab"
    输出:
    [0, 1, 2]
    解释:
    起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
    起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
    起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
    
    

    思路:我们使用双指针维护一个窗口,该窗口的大小与目标穿保持一致。判断字母异位词,我们需要保持窗口中的字母出现次数与目标串中的字母出现次数一致。

    排序类

    稳定排序:冒泡、插入、归并、基数

    不稳定排序:选择、快速、希尔、堆

    插入排序

    i位置是插入的位置,i位置前的(包括i)都是排序好了的。

    func main(){     
        arr := []int{5, 4, 3, 2, 1} 
        insert_sort(arr) 
    }  
    func insert_sort(arr []int) {        
        for i := 1; i < len(arr); i++ {           
            for j := i; j > 0; j-- {
                if arr[j] < arr[j-1] {
                    arr[j], arr[j-1] = arr[j-1], arr[j]
                }
            }
            fmt.Println(arr)
        }
    }
    
    /*
    $ go run b.go
    [4 5 3 2 1]
    [3 4 5 2 1]
    [2 3 4 5 1]
    [1 2 3 4 5]
    */
    

    按奇偶排序数组

    在一个数组中要求,偶数排在奇数之前,具体顺序无要求。

    class Solution(object):
        def sortArrayByParity(self, A):
            """
            :type A: List[int]
            :rtype: List[int]
            """
            l = len(A)
            i,j = 0,l-1
            while i<j:
                while A[i]%2==0 and i<j:
                    i+=1
                while A[j]%2==1 and i<j:
                    j-=1
                # 交换第一对前面是奇数,后面是偶数的数字对
                A[i],A[j] = A[j],A[i]
                    
            return A
    
    # 另一种实现
    class Solution:
        def sortArrayByParity(self, A: List[int]) -> List[int]:
            j = 0
            for index, v in enumerate(A):
                if A[index] % 2 == 0:
                    A[j], A[index] = A[index], A[j]
                    j += 1
            return A  
    

    go语言版本:

    func sortArrayByParity(A []int) []int {
        j := 0
        for i := range A {
            if A[i] % 2 == 0 {
                A[j], A[i] = A[i], A[j]
                j++
            }
        }
        return A
    }
    

    位运算系列

    2的幂

    给定一个整数,编写一个函数来判断它是否是2的幂次方。

    题解:对于N为2的幂的数,都有N&(N-1)=0,所以这个就是我们的判断条件。

    这个技巧可以记忆下来,在一些别的位运算的题目中也是会用到的。

    go实现:

    //go
    func isPowerOfTwo(n int) bool {
        return n > 0 && n&(n-1) == 0
    }
    

    python实现:

    class Solution:
        def isPowerOfTwo(self, n: int) -> bool:
            return n >0 and n & (n-1) == 0
    

    位1的个数

    编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

    class Solution:
        def hammingWeight(self, n: int) -> int:
            ans = 0
            while n >0:
                ans+=n%2
                n = n>>1
            return ans
    

    java实现:

    public class Solution {
        // you need to treat n as an unsigned value
        public int hammingWeight(int n) {
            int result = 0;
            int mask = 1;
            for (int i=0;i<32; i++){
                if ((n&mask)!=0){
                    result++;
                }
                mask = mask << 1;
            }
            return result;
        }
    }
    

    只出现一次的数字

    给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

    题解:肯定用位运算的方法,两个相同数字的异或运算为0.

    如果a、b两个值不同,则异或结果为1.如果a、b两个值相同,异或结果为0.

    异或还有以下性质:

    • 任意一个数和0异或仍然为自己
    • 任意一个数和自己异或是0
    • 异或操作满足交换律和结合律

    python实现:

    class Solution:
        def singleNumber(self, nums):
            """
            :type nums: List[int]
            :rtype: int
            """
            a  = 0
            for num in nums:
                a = a ^ num
            return a
    

    只出现一次的数字②

    给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

    思路:

    1. HashMap统计求解,缺点是使用了额外的空间。
    2. 数学方式:也就是说,把原数组去重,再乘以3得到的值(求和),刚好是要找的元素的2倍。
    3. 位运算:定义一种a?a?a =0的运算

    方法2的python实现:

    class Solution:
        def singleNumber(self, nums: List[int]) -> int:
            double_target = sum(set(nums))*3 - sum(nums)
            # 注意int转换,因为涉及到触发,结果以float格式返回
            return int(double_target/2)
    

    方法3,是不是就是让其二进制的每一位数都相加,最后再对3进行一个取模的过程呢?go实现:

    //go 
    func singleNumber(nums []int) int {
        number, res := 0, 0 
        for i := 0; i < 64; i++ { 
            //初始化每一位1的个数为0 
            number = 0
            for _, k := range nums { 
                //通过右移i位的方式,计算每一位1的个数
                number + = (k >> i) & 1
            }
            //最终将抵消后剩余的1放到对应的位数上
            res |= (number) % 3 << i
        }
        return res
    }
    

    缺失数字

    给定一个包含 0, 1, 2, ..., nn 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。

    示例:

    输入: [3,0,1]
    输出: 2
    输入: [9,6,4,2,3,5,7,0,1]
    输出: 8
    

    思路:

    1. 数学解法:n个连续数字的和为(n+1)*n/2
    2. 位运算:利用 两个相同的数,使用异或可以相消除 的原理。

    方法1的python实现:

    class Solution:
        def missingNumber(self, nums: List[int]) -> int:
            n = len(nums)
            total = (n+1)*n/2
            total = int(total)
            for x in nums:
                total -= x
            return total
    
    #python
    class Solution:
        def missingNumber(self, nums: List[int]) -> int:
            return sum(range(len(nums) + 1)) - sum(nums)
    

    方法2的go实现:

    //Go
    func missingNumber(nums []int) int {
        result := 0
        for i,k := range nums {
            result ^= k ^ i
        }
        return result ^ len(nums)
    }
    

    java版本:

    //java
    class Solution {
        public int missingNumber(int[] nums) {
            int res = 0;
            for(int i = 0; i < nums.length; i++ )
                res ^= nums[i] ^ i;
            return res ^ nums.length;
        }
    }
    

    二分法系列

    第一个错误的版本

    你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

    假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

    你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

    示例:

    给定 n = 5,并且 version = 4 是第一个错误的版本。
    
    调用 isBadVersion(3) -> false
    调用 isBadVersion(5) -> true
    调用 isBadVersion(4) -> true
    
    所以,4 是第一个错误的版本。 
    
    

    思路:使用二分查找快速定位出第一个错误版本。

    python实现:

    # The isBadVersion API is already defined for you.
    # @param version, an integer
    # @return a bool
    # def isBadVersion(version):
    
    class Solution:
        def firstBadVersion(self, n):
            """
            :type n: int
            :rtype: int
            """
            left = 1
            right = n
            while left < right:
                mid = left + (right-left)//2
                if isBadVersion(mid):
                    right = mid
                else:
                    left = mid + 1
            return left
        
    

    java的实现:

    //JAVA 
    public class Solution extends VersionControl {
        public int firstBadVersion(int n) { 
            int left = 1; 
            int right = n; 
            int res = n; 
            while (left <= right) { 
                int mid = left + ((right - left) >> 1);
                if (isBadVersion(mid)) {
                    res = mid;
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
            return res;
        }
    }
    

    寻找旋转排序数组中的最小值

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。

    ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

    请找出其中最小的元素。

    你可以假设数组中不存在重复元素。

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

    思路:在二分搜索中,我们找到区间的中间点并根据某些条件决定去区间左半部分还是右半部分搜索。但是麻烦的是,我们的数组被旋转了,因此肯定不能直接使用二分。那我们需要先观察一下。

    无论怎么旋转,我们可以得到一个结论:首元素 > 尾元素

    问题似乎变得简单了,旋转将原数组一分为二,并且我们已知了首元素值总是大于尾元素,那么我们只要找到将其一分为二的那个点(该点左侧的元素都大于首元素,该点右侧的元素都小于首元素),是不是就可以对应找到数组中的最小值。

    然后我们通过二分来进行查找,先找到中间节点mid,如果中间元素大于首元素,我们就把mid向右移动;如果中间元素小于首元素,我们就把mid向左移动。

    python实现:

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

    这个题目要多看几个题解,彻底吃透它!

    java实现:

    class Solution {
        public int findMin(int[] nums) {
            int left = 0;
            int right = nums.length - 1;
            while (left < right) {
                int mid = left + (right - left) / 2;
                if (nums[mid] > nums[right]) {          
                    left = mid + 1;
                } else {                                
                    right = mid;
                }
            }
            return nums[left];
        }
        
    }
    

    寻找旋转排序数组中的最小值②

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。

    ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

    请找出其中最小的元素。

    注意数组中可能存在重复的元素。

    java实现:

    class Solution {
        public int findMin(int[] nums) {
            int left = 0;
            int right = nums.length - 1;
            while (left<right) {
                int mid = left + (right-left)/2;
                if(nums[mid] > nums[right]){
                    left = mid + 1;
                }
                else if (nums[mid] < nums[right])
                {
                    right = mid;
                }
                else{
                    right--;
                }
            }
            return nums[left];
        }
    }
    

    注意边界条件。

  • 相关阅读:
    java设计模式简介
    java设计模式--单例模式
    判断整形回文数
    常用正则表达式 捕获组(分组)
    [转]十分钟搞定Vue搭建
    ActiveX界面已显示,调用方法报undefined的处理办法
    [转]纯js导出json到excel(支持chrome)
    webapi 开启gzip压缩
    webapi下载文件
    iis添加共享目录为虚拟目录
  • 原文地址:https://www.cnblogs.com/kelvinchiang/p/13454656.html
Copyright © 2020-2023  润新知