1. 程式人生 > >Leetcode解題思路總結(Easy篇)

Leetcode解題思路總結(Easy篇)

otto 一半 number steps aced 其中 運動 may copy

終於刷完了leetcode的前250道題的easy篇。好吧,其實也就60多道題,但是其中的套路還是值得被記錄的。 至於全部code,請移步github,題目大部分采用python3,小部分使用C,如有問題和建議,歡迎指正。

String

  1. 有一個string庫,可以返回各種string的匯總,很值得用。

  2. 當題目中需要實現字符串替代的時候,python中有一個自帶的translate()函數可以實現這個功能,具體可見Python3字符串替換replace(),translate(),re.sub()

  3. two pointers 在string題目中很常用,如前後pointers遍歷整個string。

Two pointers

這個方法其實就是采用兩個指針,分別指向string或者list或者linked list的不同位置進行遍歷,其實在pythonic的解法中,這種pointer的實現方式就是數組的遍歷。

eg1:141. Linked list Cycle

Given a linked list, determine if it has a cycle in it.

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
# self.next = None class Solution(object): def hasCycle(self, head): """ :type head: ListNode :rtype: bool """ if head == None: return False runningman1 = head runningman2 = head.next while runningman2 != None and
runningman2.next != None: if runningman2 == runningman1 or runningman2.next == runningman1: return True runningman2 = runningman2.next.next runningman1 = runningman1.next return False

這個題目是一個關於linked list的題目,此解答用了兩個運動員(two pointers)的方法,一個跑的慢,一個跑的快,如果有循環,則跑的快的人一定可以追上跑的慢的人。其實解題重要的還是邏輯思路,而實現方法真的是次要的。

其實這個題目還有一種方法也很棒,具體可移步到github

eg2: 167. Two Sum II - Input array is sorted

Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.

這個題采用的是前後two pointers的方法進行夾擊:

class Solution:
    def twoSum(self, numbers, target):
        """
        :type numbers: List[int]
        :type target: int
        :rtype: List[int]
        """
        i,j = 0,len(numbers)-1
        while(numbers[i]+numbers[j]!=target):
            if numbers[i]+numbers[j] > target:
                j -= 1
            else:
                i += 1
        return i+1,j+1

eg3: 234. Palindrome Linked List

Given a singly linked list, determine if it is a palindrome.
Example: Input: 1->2->2->1 Output: true

1、這個問題可以拆解成兩個問題: 一是找到該單鏈表的中間位置,二是反轉鏈表

2、對於第一個問題(找到單鏈表的中間位置),可以通過two pointers 的方法進行,一個pointer每次走一步,另一個pointer每次走兩步,當每次走兩步的pointer到達final時,另一個pointer剛好在一半位置

3、對於第二個問題(反轉單鏈表),可以標準性的分割成四個步驟:

nxt = slow.next slow.next = node node = slow slow = nxt

class Solution:
    def isPalindrome(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        ## find the mid
        slow = fast = head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
        ## reverse the second half
        node = None
        while slow:
            nxt = slow.next
            slow.next = node
            node = slow
            slow = nxt
        ## compare two halves
        while node:
            if node.val == head.val:
                node = node.next
                head = head.next
            else:
                return False
        return True            

bin運算

其實我以前一直不知道python也可以進行位運算,直到我發現了這個題目:

eg4: 136. Single Number

Given a non-empty array of integers, every element appears twice except for one. Find that single one.

我的做法很蠢,采用的是hashmap的做法,創建一個set,然後逐個適用in運算符進行比較。其實這道題用異或(xor)是最好的方式,因為兩個相同的數進行異或的二進制計算會返回全零。

求和後相減也是一個好的方法。

def singleNumber1(self, nums):
    return 2*sum(set(nums))-sum(nums)
    
def singleNumber2(self, nums):
    return reduce(lambda x, y: x ^ y, nums)
    
def singleNumber3(self, nums):
    return reduce(operator.xor, nums)

這裏借用菜鳥教程裏的一個圖總結python裏的位運算圖:

技術分享圖片

eg5: 231. Power of Two

Given an integer, write a function to determine if it is a power of two.

這個題也一樣,我采用的方法如下:如果一個數為2的冪,那麽他的bin形式則只有一個1,

class Solution:
    def isPowerOfTwo(self, n):
        """
        :type n: int
        :rtype: bool
        """
        if n <= 0:
            return False
        return bin(n).count(‘1‘) == 1

還有一種更簡單的方法,那就是判斷n&(n-1) == 0

note:&位運算符

Hash Table

其實對於python再說,hashtable直接用dict或者set就可以解決。

eg6: 205. Isomorphic Strings

Given two strings s and t, determine if they are isomorphic. Two strings are isomorphic if the characters in s can be replaced to get t. All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself.

好的方法就是將s[i]和t[i]結對,然後再進行比較,使用set()即可:

def isIsomorphic(self, s, t):
    return len(set(zip(s, t))) == len(set(s)) == len(set(t))

實際上,這道題我采用的是用兩個dict,從頭遍歷s和t,若元素再dict中沒有則add進dict,value值為此時s或t的index,若有則判斷兩個dict的當前元素是否是同一個value。

然而,其實用一個dict也可以搞定:

def isIsomorphic(self, s, t):
    """
    :type s: str
    :type t: str
    :rtype: bool
    """
    found = {}
    ## 這個dict的key是s[i],value值是t[i]
    
    for i in range(len(s)):
        if s[i] in found:
            if not found[s[i]] == t[i]:
                return False
        else:
            if t[i] in found.values():
                return False
            found[s[i]] = t[i]
    return True

另一種方法為使用find()函數。具體見github

eg7: 169. Majority Element

Given an array of size n, find the majority element. The majority element is the element that appears more than ? n/2 ? times. You may assume that the array is non-empty and the majority element always exist in the array.

HashMap allows us to count element occurrences efficiently.

以下是我的原始方法:

class Solution:
    def majorityElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        countdict = {}
        for num in nums:
            if num not in countdict:
                countdict[num] = 1
            else:
                countdict[num] += 1
        return max(countdict.items(),key=lambda x:x[1])[0]

不過,原來有一個collections.Counter()可以自動計數的啊

class Solution:
    def majorityElement(self, nums):
        counts = collections.Counter(nums)
        return max(counts.keys(), key=counts.get)

DP問題

在easy的題目中,最典型的就數買股票問題了。簡單來說,動態規劃問題就是每次問題的處理與上一次問題處理的結果有關

eg8: 121. Best Time to Buy and Sell Stock

Say you have an array for which the ith element is the price of a given stock on day i. If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit. Note that you cannot sell a stock before you buy one.

class Solution:
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        if not prices:
            return 0
        else:
            maxprofit = 0
            minbuy = float(‘inf‘)
            for p in prices:
                maxprofit = max(maxprofit,p-minbuy)
                minbuy = min(minbuy,p)
            return maxprofit

eg9: 70. Climbing Stairs

You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

這也是個更典型的DP問題, 註意為了節省時間,為了不反復求解相同的子問題,可以采用1. 帶備忘的自頂向下法 2. 自底向上法

下面為帶備忘的方法:

class Solution:
    def __init__(self):
        self.demo = {}
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n in {1,2,3}:
            return n
        if n in self.demo:
            return self.demo[n]
        else:
            value = self.climbStairs(n-1) + self.climbStairs(n-2)
            self.demo[n] = value
            return  value

eg10: 198. House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night. Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

DP問題最重要的部分在於對於不同問題領域,最優子結構不同體現在兩個方面:

1、原問題的最優解中涉及多少個子問題

2、在確定最優解使用哪些子問題時,我們需要考察多少中選擇

所以,在coding之前,最好是畫出問題的子問題圖

對於這個例子來說,原問題需要涉及兩個相鄰的子問題,且只需比較demo[i]+num > demo[i+1]

class Solution:
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return 0
        demo = {}
        demo[0] = nums[0]
        demo[1] = max(nums[0:2])
        for i, num in enumerate(nums[2:]):
            if demo[i]+num > demo[i+1]:
                value = demo[i]+num
            else:
                value = demo[i+1]
            demo[i+2] = value
        return demo[len(nums)-1]

Tree

Tree的操作我喜歡用recursion,將子結點看成新的子樹進行遞歸。

eg11: 107. Binary Tree Level Order Traversal II

Given a binary tree, return the bottom-up level order traversal of its nodes‘ values. (ie, from left to right, level by level from leaf to root).

這個問題沒有用遞歸,而是采用queue的方式進行解答,這個也很典型。

隊列中保存處理的樹??的當前深度(depth)的所有結點,每deq一個結點,enq其左右子結點,當deq掉所有當前深度結點時,隊列中剩下該樹??下一深度所有結點。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def levelOrderBottom(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[int]]
        """
        queue = Queue()
        output = []
        if root:
            queue.enqueue(root)
            output.append([root.val])
        while not queue.is_empty():
            levelout = []
            for _ in range(queue.size()):
                node = queue.dequeue()
                if node.left != None:
                    levelout.append(node.left.val)
                    queue.enqueue(node.left)
                if node.right != None:
                    levelout.append(node.right.val)
                    queue.enqueue(node.right)
            if levelout:
                output.insert(0,levelout)
        return output

這道題也可以使用stack解答,只要給每一個node加一個depth的標簽即可,即保存一個tuple。具體見github

eg12: 108. Convert Sorted Array to Binary Search Tree

Given an array where elements are sorted in ascending order, convert it to a height balanced BST. For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.

對於這個題目,最適合用遞歸了。每次取到array的中間值,然後左右生成兩個子樹,依次遞歸。下面的代碼是借用別人的回答:

def sortedArrayToBST(self, num):
    if not num:
        return None

    mid = len(num) // 2

    root = TreeNode(num[mid])
    root.left = self.sortedArrayToBST(num[:mid])
    root.right = self.sortedArrayToBST(num[mid+1:])

    return root

eg13: 110. Balanced Binary Tree

Given a binary tree, determine if it is height-balanced. A binary tree in which the depth of the two subtrees of every node never differ by more than 1.

樹??的問題通常可以用兩種方法解決:

  • 一種是top down,這種方法就是從上而下的叠代或者遞歸
  • 一種是bottom up,這種方法是利用DFS的思想,這種方法通常要使用stack
class Solution:
    def isBalanced(self, root):
        """
        :type root: TreeNode
        :rtype: bool
        """
        if not root:
            return True
        else:
            return self.isBalanced(root.left) and self.isBalanced(root.right) and abs(self._TreeDepth(root.right)-self._TreeDepth(root.left))<=1
            
    def _TreeDepth(self, root):
        if root == None:
            return 0
        else:
            return max(self._TreeDepth(root.left), self._TreeDepth(root.right))+1

我用的第一種方法,但可以修改成如下形式,不用每次都求得每個node的深度,而是在每次求深度的過程中自動判斷該node是否平衡:

class Solution(object):
    def isBalanced(self, root):
            
        def check(root):
            if root is None:
                return 0
            ## 判斷左右子樹是否平衡,並返回深度。若不平衡,則返回-1
            leftd  = check(root.left)
            rightd = check(root.right)
            if leftd == -1 or rightd == -1 or abs(leftd - rightd) > 1:
                return -1
            return 1 + max(leftd, rightd)
            
        return check(root) != -1

eg14: 112. Path Sum

Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.

這道題我用的是DFS的思想,但是才baet了25%的夥伴。在看discussion的過程中,發現人的智商真是無限的。

下面的代碼采用up down的形式,相當於把樹??的每一層都隔絕,是一個貪心問題。

貪心算法通常都是自頂向下的設計,做出一個選擇,然後求解剩下的那個子問題。

class Solution:
    def hasPathSum(self, root, sum):
        if not root:
            return False
        ## 如果是葉結點
        if not root.left and not root.right and root.val == sum:
            return True
        sum -= root.val
        
        return self.hasPathSum(root.left, sum) or self.hasPathSum(root.right, sum)

其他

如果說leetcode的神奇之處在哪,那一定不是題目的套路,而是反套路。

eg15: 204. Count Primes

Count the number of prime numbers less than a non-negative number, n.

要求比一個數n小的所有質數的數量,那麽就排除掉所有不是質數的數。

怎麽排除呢?如果一個數是非質數,那麽這個數一定是所有<sqrt(n)的數中某一個數的倍數。

class Solution:
    def countPrimes(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n < 3:
            return 0
        primes = [True] * n
        primes[0] = primes[1] = False
        for i in range(2, int(n**0.5)+1):
            primes[i*2::i] = [False] * len(primes[i*2::i])
        return sum(primes)

eg16: 189. Rotate Array

Given an array, rotate the array to the right by k steps, where k is non-negative.

Example: Input: [1,2,3,4,5,6,7] and k = 3 Output: [5,6,7,1,2,3,4] Explanation: rotate 1 steps to the right: [7,1,2,3,4,5,6] rotate 2 steps to the right: [6,7,1,2,3,4,5] rotate 3 steps to the right: [5,6,7,1,2,3,4]

這道題很多種解法,用python的話最容易的解法應該屬於slice直接替換,但是每一次slice都會產生一個額外的copy,從而消耗了空間。

其實這道題就簡單直白的一個個元素依次往前替換就可以了,這樣可以只消耗O(1)的空間。

class Solution:
    def rotate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: void Do not return anything, modify nums in-place instead.
        """
        if k !=0 and k!=len(nums):
            k = k % len(nums)
            count = len(nums)
            start = 0
            while count>1:
                current = (start + k) % len(nums)
                while current != start: 
                    temp = nums[current]
                    nums[current] = nums[start]
                    nums[start] = temp
                    current = (current + k)  % len(nums)
                    count -= 1
                start += 1
                count -= 1

eg17: 172. Factorial Trailing Zeroes

Given an integer n, return the number of trailing zeroes in n!. Example: Input: 3 Output: 0 Explanation: 3! = 6, no trailing zero.

這是一道數學題,是一道考智商的題目。我真的沒有這麽聰明想出好的辦法,可以有人想得出啊! 這裏引用別人的一番話:

Because all trailing 0 is from factors 5 * 2. But sometimes one number may have several 5 factors, for example, 25 have two 5 factors, 125 have three 5 factors. In the n! operation, factors 2 is always ample. So we just count how many 5 factors in all number from 1 to n.

所以說,這個題目所有的0都是由於5提供的。所以每隔5個數有一個5,每隔55個數有兩個5,每隔55*5個數有3個5...

class Solution:
    def trailingZeroes(self, n):
        """
        :type n: int
        :rtype: int
        """
        return 0 if n == 0 else n // 5 + self.trailingZeroes(n // 5)

Reference:

  1. https://leetcode.com
  2. 《算法導論》

Leetcode解題思路總結(Easy篇)