数据结构与算法分类练习--二分查找 二叉排序 位操作

在有序表中查找元素常常使用二分查找(Binary Search),又称折半查找。套路总结

# 这里必须是<=,保证可以检测到最后一个数值
while (left <= right) {
    # 在其它语言中直接使用(left + right) / 2可能会超出边界值 > 2^32,但在python中,超出机器自身所能支持的范围时,会自动转换大数计算。
    int mid = (right - left)/2 + left;#防止溢出
    if (array[mid] ? key) {
        right = mid - 1;
    }
    else {
        left = mid + 1;
    }
}
return ?;
  • 第二个?首先判断出是返回left,还是返回right

因为我们知道最后跳出while (left <= right)循环条件是right < left,且right = left - 1。最后right和left一定是卡在"边界值"的左右两边,如果是比较值为key,查找小于等于(或者是小于)key的元素,则边界值就是等于key的所有元素的最左边那个,其实应该返回left。

  • 第一个?判断出比较符号。判断什么情况下还需要移动左右边界。

    >

    1.第一个大于key的元素: > left

    2.第一个等于或者大于: >= left

    <

    3.最后一个小于: >= right

    4.最后一个等于或者小于: > right

    =

    5.最后一个等于: > right

    6.第一个等于: >= left

二分查找法之需要O(log n)让它成为十分高效的算法。不过使用它的条件也比较苛刻:必须有序。这就需要我们在构建数组的时候进行排序,这就遇到第二个问题:数组读取效率O(1),可是它的插入和删除某个元素的效率却是O(n)。因而导致构建有序数组变成低效的事情。解决这个问题更好的方法是构建二叉排序树,能高效的(O(n log n))构建有序元素集合,又能如同二分查找法一样快速(O(log n))的搜寻目标数值。

二叉排序树(Binary Search Tree)又称二叉搜索(查找)树,定义和特征如下:

  • 若它的左子树非空,则左子树上所有结点的权值都比根结点的权值小;
  • 若它的右子数非空,则右子树上所有结点的权值都比根结点的权值大;
  • 左、右子树本身又是一棵二叉排序树。

位运算的速度比一般运算快,掌握了位运算,就能够在程序编写时更加灵活,提高程序效率。在算法中常使用二进制数1~n位的0/1状态来表示一个由1~n组成的集合。详参Matrix67的《位运算简介及实用技巧》(一)~(四)

Search in Rotated Sorted Array(数组的二分查找)

Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

这道题的难点在于我们不知道原数组在哪儿旋转了,我们可以把所有情况列出来寻找规律。加粗部分为升序排列,我们可以拿中位数和最左侧做对比来判断目标值在哪一侧。

0  1  2   4  5  6  7

7  0  1   2  4  5  6

6  7  0   1  2  4  5

5  6  7   0  1  2  4

4  5  6   7  0  1  2

2  4  5   6  7  0  1

1  2  4   5  6  7  0

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left, right = 0, len(nums) - 1
        
        while left <= right:
            mid = left + (right - left) / 2
            if nums[mid] == target:
                return mid
            elif (nums[mid] >= nums[left] and nums[left] <= target < nums[mid]) or \
                (nums[mid] < nums[left] and not nums[mid] < target <= nums[right]):
                right = mid - 1
            else:
                left = mid + 1
        return -1

Search in Rotated Sorted Array II(数组的二分查找II)

Follow up for "Search in Rotated Sorted Array": What if duplicates are allowed?

如果可以有重复值,就会出现来面两种情况,[1 3 1 1 1] 和 [1 1 1 3 1],对于这两种情况中间值等于最左值时,目标值3既可以在左边又可以在右边,那怎么办么,只要把最左值向右移一位即可,如果还相同则继续移,直到移到不同值为止,剩下处理方式和上题类似。

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: bool
        """
        left, right = 0, len(nums) - 1
        
        while left <= right:
            mid = left + (right - left) / 2
            if nums[mid] == target:
                return True
            elif nums[mid] == nums[left]:
                left += 1
            elif (nums[mid] > nums[left] and nums[left] <= target < nums[mid]) or \
                (nums[mid] < nums[left] and not nums[mid] < target <= nums[right]):
                right = mid - 1
            else:
                left = mid + 1
        return False

Search for a Range(区间二分查找)

Given a sorted array of integers, find the starting and ending position of a given target value.

Your algorithm's runtime complexity must be in the order of O(log n).

If the target is not found in the array, return [-1, -1].

For example,

Given [5, 7, 7, 8, 8, 10] and target value 8, return [3, 4].

使用两次二分查找法,第一次找到左边界,第二次找到右边界。

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        left = self.binarySearch(lambda x, y: x >= y, nums, target)
        if left >= len(nums) or nums[left] != target:
            return [-1, -1]
        right = self.binarySearch(lambda x, y: x > y, nums, target)
        return [left, right - 1]

    def binarySearch(self, compare, nums, target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right - left) / 2
            if compare(nums[mid], target):
                right = mid - 1
            else:
                left = mid + 1
        return left

Convert Sorted List to Binary Search Tree(排序链表转为二叉排序树)

Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST.

二分查找法每次需要找到中点,而链表的查找中间点可以通过快慢指针来操作,也可以计算出链表的总长度。找到中点后,要以中点的值建立一个数的根节点,然后需要把原链表断开,分为前后两个链表,都不能包含原中节点,然后再分别对这两个链表递归调用原函数,分别连上左右子节点即可。

class Solution(object):
    def sortedListToBST(self, head):
        """
        :type head: ListNode
        :rtype: TreeNode
        """
        current, length = head, 0
        while current is not None:
            current, length = current.next, length + 1
        self.head = head
        return self.sortedListToBSTRecu(0, length)
    
    def sortedListToBSTRecu(self, start, end):
        if start == end:
            return None
        mid = start + (end - start) / 2
        current = TreeNode(self.head.val)
        current.left = self.sortedListToBSTRecu(start, mid) 
        self.head = self.head.next
        current.right = self.sortedListToBSTRecu(mid + 1, end)
        return current

Convert BST to Greater Tree(二叉排序树的遍历与改造)

Given a Binary Search Tree (BST), convert it to a Greater Tree such that every key of the original BST is changed to the original key plus sum of all keys greater than the original key in BST.

Example:

Input: The root of a Binary Search Tree like this:

    5

   / \

 2   13

Output: The root of a Greater Tree like this:  

   18

   / \

20 13

将中序遍历左根右的顺序逆过来,变成右根左的顺序,这样就可以反向计算累加和sum,同时更新结点值。

class Solution(object):
    def convertBST(self, root):
        """
        :type root: TreeNode
        :rtype: TreeNode
        """
        self.sum = 0
        def convertTree(node):
            if not node:
                return 
            convertTree(node.right)
            node.val += self.sum
            self.sum = node.val
            convertTree(node.left)
        convertTree(root)
    	return root

Kth Smallest Element in a BST(二叉排序树中的第K小的数)

Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.

根据BST的性质,如果用中序遍历所有的节点就会得到一个有序数组,中序遍历最先遍历到的是最小的结点,我们可以利用栈和一个计数器,中序遍历每一个结点,计数器自增1,当计数器到达k时,返回当前结点值即可。

class Solution(object):
    def kthSmallest(self, root, k):
        """
        :type root: TreeNode
        :type k: int
        :rtype: int
        """
        s, cur, rank = [], root, 0
        while s or cur:
            if cur:
                s.append(cur)
                cur = cur.left
            else:
                cur = s.pop()
                rank += 1
                if rank == k:
                    return cur.val
                cur = cur.right

Subsets(使用位运算表示集合)

Given a set of distinct integers, S, return all possible subsets.

  • Elements in a subset must be in non-descending order.
  • The solution set must not contain duplicate subsets.

把数组中所有的数分配一个状态,true表示这个数在子集中出现,false表示在子集中不出现,那么对于一个长度为n的数组,每个数字都有出现与不出现两种情况,所以共有2的n次方种情况,那么我们把每种情况都转换出来就是子集。

class Solution(object):
    def subsets1(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        result = [ ]
        i, count = 0, 1 << len(nums)
        while i < count:
            cur = [ ]
            for j in range(len(nums)):
                if i & 1 << j:
                    cur.append(nums[j])
            result.append(cur)
            i += 1
        return result

另一种方法是可以一位一位的往上叠加,比如对于[1,2,3]来说,最开始是空集,那么我们现在要处理1,就在空集上加1,为[1],现在我们有两个自己[]和[1],下面我们来处理2,我们在之前的子集基础上,每个都加个2,可以分别得到[2],[1, 2],那么现在所有的子集合为[], [1], [2], [1, 2],同理处理3的情况可得[3], [1, 3], [2, 3], [1, 2, 3], 再加上之前的子集就是所有的子集合了。

class Solution(object):
    def subsets(self, nums):
        result = [[]]
        for num in nums:
            result += [subset + [num] for subset in result]
        return result
        
    def subsets1(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        result = [ ]
        i, count = 0, 1 << len(nums)
        while i < count:
            cur = [ ]
            for j in range(len(nums)):
                if i & 1 << j:
                    cur.append(nums[j])
            result.append(cur)
            i += 1
        return result

Single Number (every element appears twice except for one)

Given an array of integers, every element appears twice except for one. Find that single one.

0与0异或是0,1与1异或也是0。根据这个特点,我们把数组中所有的数字都异或起来,则每对相同的数字都会得0,然后最后剩下来的数字就是那个只有1次的数字。

class Solution(object):
    def singleNumber1(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        return reduce(operator.xor, nums)
    
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        x = 0
        for a in nums:
            x ^= a
        return x

Single Number II (every element appears three times except for one)

Given an array of integers, every element appears three times except for one. Find that single one.

把数组中数字的每一位累加起来对3取余,剩下的结果就是那个单独数组该位上的数字,由于我们累加的过程都要对3取余,那么每一位上累加的过程就是0->1->2->0,换成二进制的表示为00->01->10->00,用one, two分别表示第一位和第二位,可以推算出加1后的状态变化值。

class Solution(object):
    def singleNumber(self, nums):
        a = set(nums);
        a = sum(a) * 3 - sum(nums);
        a = a/2;
        return a;
    def singleNumber1(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        one, two = 0, 0
        for i in nums:
            one = (one ^ i) & ~two
            two = (two ^ i) & ~one
        return one

Single Number III

Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.

Given nums = [1, 2, 1, 3, 2, 5], return [3, 5]

如果能想办法把原数组分为两个小数组,不相同的两个数字分别在两个小数组中,就可以利用single number1中的解法了。做法如下:先把原数组全部异或起来,那么我们会得到一个数字,它是两个不相同的数字异或的结果,我们取出其中任意一位为‘1’的位,为了方便起见,我们用 a &= -a 来取出最右端为‘1’的位,然后和原数组中的数字挨个相与,那么我们要求的两个不同的数字就被分到了两个小组中。

class Solution(object):
    def singleNumber(self, nums):
        axorb =  reduce(operator.xor, nums)
        bit = axorb & -axorb
        a = 0
        for i in nums:
            if i & bit:
                a ^= i
        return [a, a ^ axorb]
    
    def singleNumber1(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        a = reduce(operator.xor, nums)
        bit = a & -a
        result = [0, 0]
        for i in nums:
            result[bool(i & bit)] ^= i
        return result

猜你喜欢

转载自blog.csdn.net/ZJL0105/article/details/81322219