leetcode 刷题视频(6) - 二分搜索和二叉查找树

二分搜索和二叉查找树

二叉查找树又叫二叉排序树

预备知识 二分查找

折半查找。假设元素升序排列

递归形式

#include <vector>
using namespace std;
bool binary_search(vector<int> &sort_array, int begin, int end, int target) {
    if (begin > end) {
        return false;
    }
    int mid = (begin + end) / 2;
    if (target == sort_array[mid]) {
        return true;
    } else if (target < sort_array[mid]) {
        return binary_search(sort_array, begin, mid - 1, target);
    } else {
        return binary_search(sort_array, mid + 1, end, target);
    }
}

二分搜索的范围都用闭区间。

循环形式

#include <vector>
using namespace std;
bool binary_search(vector<int> &sort_array, int target) {
    int begin = 0;
    int end = sort_array.size() - 1;
    while (begin <= end) {
        int mid = (begin + end) / 2;
        if (target == sort_array[mid]) {
            return true;
        } else if (target < sort_array[mid]) {
            end = mid - 1;
        } else {
            begin = mid + 1;
        }
    }
    return false;
}

问题1 插入位置

Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

Example 1:

Input: nums = [1,3,5,6], target = 5
Output: 2

Example 2:

Input: nums = [1,3,5,6], target = 2
Output: 1

Example 3:

Input: nums = [1,3,5,6], target = 7
Output: 4

Example 4:

Input: nums = [1,3,5,6], target = 0
Output: 0

Example 5:

Input: nums = [1], target = 0
Output: 0

Constraints:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums contains distinct values sorted in ascending order.
  • -104 <= target <= 104

链接:https://leetcode-cn.com/problems/search-insert-position/

思考:

  1. 当target在nums中出现时,二分查找的流程没有变化。

  2. 当target在nums中没有出现时:

    (1) 如果target<nums[mid],并且target>nums[mid-1] or mid==0,那应该插入到mid

    (2) 如果target>nums[mid],并且target<nums[mid+1] or mid==nums.size()-1,那应该插入到mid+1

视频中的代码视图在每一次指针移动的时候都进行一个操作,

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int index = -1;
        int begin = 0;
        int end = nums.size() - 1;
        while (index == -1) {
            int mid = (begin + end) / 2;
            if (target == nums[mid]) {
                index = mid;
            } else if (target < nums[mid]) { // 每次指针位置移动之前都判断一下mid是不是要找的位置
                if (mid == 0 || target > nums[mid-1]) {
                    index = mid;
                }
                end = mid - 1;
            } else if (target > nums[mid]) {
                if (mid == nums.size()-1||target<nums[mid+1]) {
                    index = mid + 1;
                }
                begin = mid + 1;
            }
        }
        return index;
    }
};

更简单的方法是直接返回begin

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int begin = 0;
        int end = nums.size() - 1;
        int mid = 0;
        while (begin <= end) {
            mid = (begin + end) / 2;
            if (target == nums[mid]) {
                return mid;
            } else if (target < nums[mid]) {
                end = mid - 1;
            } else {
                begin = mid + 1;
            }
        }
        return begin;
    }
};

begin mid end 会呈现什么特征?

  1. 最终是不是一定会聚集到一点,然后 begin+1 或 end-1 从而退出循环?好像是的。(中途退出就不是了)
  2. begin最终一定会在比target大的位置?是
  3. end一定会在比target小的位置?是
  4. 二分法为什么不会越界,即mid<0mid>=len?因为end=-1时,end=-1时,begin=0begin<=end不成立,循环退出

问题2 区间查找

Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value.

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

Follow up: Could you write an algorithm with O(log n) runtime complexity?

Example 1:

Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]

Example 2:

Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]

Example 3:

Input: nums = [], target = 0
Output: [-1,-1]

Constraints:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums is a non-decreasing array.
  • -10^9 <= target <= 10^9

链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/

把问题拆分成求左右端点。

为什么不能先找到一个数,再往左右搜索?因为最差情况下会退化到 O ( N ) O(N) O(N)

查找区间左端点时,增加限制条件

  • 当 target == nums[mid] 时,若此时 nums[mid-1]<target,则说明mid是区间左端点,返回;否则设置区间右端点为mid-1

在查找右端点时,当 nums[mid+1]>target时则mid是右端点。

上面的算法都是在查找过程中进行操作,所以这应该是二分查找的通用思路。

测试技巧:测试一段数据,而不是一个一个测试

int main() {
    int test[] = {5, 7, 7, 8, 8, 8, 8, 10};
    vector<int> nums;
    Solution solve;
    nums.reserve(8);
    for (int & i : test) {
        nums.push_back(i);
    }
    for (int i = 0; i < 12; i++) {
        vector<int> result = solve.searchRange(nums, i);
        printf("%d : [%d, %d]\n", i, result[0], result[1]);
    }
    return 0;
}

问题3 旋转数组查找

You are given an integer array nums sorted in ascending order, and an integer target.

Suppose that nums 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]).

If target is found in the array return its index, otherwise, return -1.

Example 1:

Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

Example 2:

Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1

Example 3:

Input: nums = [1], target = 0
Output: -1

Constraints:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • All values of nums are unique.
  • nums is guranteed to be rotated at some pivot.
  • -10^4 <= target <= 10^4

链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array/

个人方法:

先找旋转数组的起点。(经测试速度慢)

class Solution {
public:
    int search(vector<int> &nums, int target) {
        int offset = findBegin(nums);
        cout << "offset=" << offset << endl;
        int begin = 0;
        int end = int(nums.size()) - 1;
        for (int i = 0; i < nums.size(); i++) {
            cout << nums[i] << ' ';
        }
        cout << endl;
        while (begin <= end) {
            int mid = (begin + end) / 2;
            int mid_offset = (mid + offset) % int(nums.size());
            if (nums[mid_offset] == target) {
                cout << "mid=" << mid << endl;
                cout << "nums[mid_offset]"
                     << "=nums[" << mid_offset << "]=" << nums[mid_offset]
                     << endl;
                return mid_offset;
            } else if (target < nums[mid_offset]) {
                end = mid - 1;
            } else {
                begin = mid + 1;
            }
        }
        return -1;
    }

private:
    // 查找旋转数组中最小的位置在哪
    static int findBegin(vector<int> &nums) {
        if (nums.size() < 2)
            return 0;
        else if (nums.size() == 2)
            return nums[0] < nums[1] ? 0 : 1;
        int begin = 0;
        int end = int(nums.size()) - 1;
        while (begin < end - 1) { // break when begin==end-1
            int mid = (begin + end) / 2;
            if (nums[begin] > nums[mid]) {
                end = mid;
            } else if (nums[mid] > nums[end]) {
                begin = mid;
            } else { // 如果从 begin 到 end 是有序的,则返回 begin
                return begin;
            }
        }
        return end;
    }
};

视频思路:

数据的排布一共可以分为三种情况

(1)(2)(3)
在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 当 target < \lt <*mid时,

    • 当*begin ≤ \le *mid时,说明是情况1或者3,
      • 当target ≥ \ge *begin,说明应该在[begin,mid-1]中找
      • 当target < < <*begin,说明应该在[mid+1,end]中找(*begin==*mid的情况合并到这里面)
    • 当*begin > \gt >*mid时,说明是情况2,
      • 只能在[begin,mid-1]中找
  • 当 target>*mid 时,

    • 当*begin < \lt <*mid时,说明是情况1或者3,【b】
      • 只能在区间[mid+1,end]中查找
    • 当*begin > \gt >*mid时,说明是情况2
      • 如果*target ≥ \ge *begin,说明应该在[begin,mid-1]中查找
      • 如果*target < \lt <*begin,说明应该在[mid+1,end]中查找
    • 当*begin==*mid时,说明是情况1或3,所以应该合并到情况b中
class Solution {
public:
    int search(vector<int> &nums, int target) {
        int begin = 0;
        int end = nums.size() - 1;
        while (begin <= end) {
            int mid = (begin + end) / 2;
            if (target == nums[mid]) {
                return mid;
            } else if (target < nums[mid]) {
                if (nums[begin] <= nums[mid]) {
                    if (target >= nums[begin]) {
                        end = mid - 1;
                    } else {
                        begin = mid + 1;
                    }
                } else {
                    end = mid - 1;
                }
            } else {
                if (nums[begin] <= nums[mid]) {
                    begin = mid + 1;
                } else {
                    if (target >= nums[begin]) {
                        end = mid - 1;
                    } else {
                        begin = mid + 1;
                    }
                }
            }
        }
        return -1;
    }
};

执行时间是4ms的范例,别人的代码:

int search(vector<int> &nums, int target) {
    if (nums.size() == 0) return -1;
    int left = 0, right = nums.size() - 1, mid;
    int begin = nums[0];
    int end = nums[right];
    while (left <= right) {
        mid = (left + right) / 2;
        if (nums[mid] == target) return mid;
        if (begin <= nums[mid]) {  // 如果左半部分是有序的: 注意 等号是为了解决mid==0的情况
            if (begin <= target && target < nums[mid]) { // 如果target在左半部分,第二个小于号没有等于,因为在上上行处理了等于的情况
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        } else {  // 如果右半部分是有序的
            if (nums[mid] < target && target <= end) { // 如果target在右半部分
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }
    return -1;
}

在这里插入图片描述

发现其实跟视频里的思路类似,只不过划分的情况更简单了。

预备知识 二叉查找树

二叉查找树(二叉排序树)

左子树的所有节点都小于等于根节点的值,右子树的所有节点都大于等于根节点的值。

堆排序中的数是根节点大于所有节点的值。

(上述两种树都是递归定义的)

二叉查找树插入节点

将待插入节点(insert_node),插入至以node为根的二叉查找树中:

如果 insert_node > node:

​ 如果 node 有左子树,则递归的将该节点插入至左子树为根的二叉排序树中

​ 否则,将node->left赋值为该节点地址

否则:

​ 如果有右子树,则递归的将该节点插入至右子树为根的二叉排序树中

​ 否则,将node->right赋值为该节点地址

void BST_insert(TreeNode *node, TreeNode *insert_node) {
    if (insert_node->val < node->val) {
        if (node->left) {
            BST_insert(node->left, insert_node);
        } else {
            node->left = insert_node;
        }
    } else {
        if (node->right) {
            BST_insert(node->right, insert_node);
        } else {
            node->right = insert_node;
        }
    }
}

查找数值

如果value等于当前查看的node的节点值:返回真

如果value小于当前node节点值:

​ 如果有左子树,继续在左子树中查找值

​ 否则返回假

否则:

​ 如果当前节点有右子树,继续在右子树中查找

​ 否则返回假

bool BST_search(TreeNode *node, int value) {
    if (node->val == value) {
        return true;
    }
    if (node->val > value) {
        if (node->left) {
            return BST_search(node->left, value);
        } else {
            return false;
        }
    } else {
        if (node->right) {
            return BST_search(node->right, value);
        } else {
            return false;
        }
    }
}

问题4 二叉树编码与解码

Serialization is converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary search tree. There is no restriction on how your serialization/deserialization algorithm should work. You need to ensure that a binary search tree can be serialized to a string, and this string can be deserialized to the original tree structure.

The encoded string should be as compact as possible.

Example 1:

Input: root = [2,1,3]
Output: [2,1,3]

Example 2:

Input: root = []
Output: []

Constraints:

  • The number of nodes in the tree is in the range [0, 10^4].
  • 0 <= Node.val <= 10^4
  • The input tree is guaranteed to be a binary search tree.

链接:https://leetcode-cn.com/problems/serialize-and-deserialize-bst/

思路:先序遍历,用逗号分割各数字,然后再用逗号解码。先序是先根。

在这里插入图片描述

问题的关键是,知道一颗二叉查找树,推断出按什么顺序插入节点才能构造出这样的树。
对于 8--3--1这条线,只要保证是按8,3,1的顺序插入的,广度优先搜索和深度优先搜索都无所谓。

class Codec {
public:
    // Encodes a tree to a single string.
    string serialize(TreeNode *root) {
        string data;
        BST_preorder(root, data);
        return data;
    }

    // Decodes your encoded data to tree.
    TreeNode *deserialize(string data) {
        if (data.length() == 0) return nullptr; // 这一行如果去掉就会执行出错
        vector<TreeNode*>node_vec;
        int val = 0;
        for (char i : data) {
            if (i == ',') {
                node_vec.push_back(new TreeNode(val));
                val = 0;
            } else {
                val = val * 10 + i - '0';
            }
        }
        for (int i = 1; i < node_vec.size(); i++) {
            BST_insert(node_vec[0], node_vec[i]);
        }
        return node_vec[0];
    }
private:
    static void int2string(int val, string &str_val) {
        string tmp;
        while (val) {
            tmp += val % 10 + '0';
            val = val / 10;
        }
        for (int i = int(tmp.length()) - 1; i >= 0; i--) {
            str_val += tmp[i];
        }
        str_val += ',';
    }

    static void BST_preorder(TreeNode*node, string&data) {
        if (!node) return;
        string str_val;
        int2string(node->val, str_val);
        data += str_val;
        BST_preorder(node->left, data);
        BST_preorder(node->right, data);
    }

    static void BST_insert(TreeNode*root, TreeNode*node) {
        // 有个问题,有等号怎么办?
        // 提交时大于号还是大于等于号都行
        if (root->val > node->val) {
            if (root->left) {
                BST_insert(root->left, node);
            } else {
                root->left = node;
            }
        } else {
            if (root->right) {
                BST_insert(root->right, node);
            } else {
                root->right = node;
            }
        }
    }
};

问题5 逆序数

You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

Example 1:

Input: nums = [5,2,6,1]
Output: [2,1,1,0]
Explanation:
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.

Constraints:

  • 0 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/

这是一道重复的题。新思路。

在这里插入图片描述

在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;

struct BSTNode {
    int val;
    int count; // 有多少数字小于等于当前node
    BSTNode *left;
    BSTNode *right;
    BSTNode(int x) : val(x), left(nullptr), right(nullptr), count(0) {}
};
void BST_insert(BSTNode *node, BSTNode *insert_node, int &count_small) {
    if (node->val >= insert_node->val) { // 必须是<=
        node->count++;
        if (node->left) {
            BST_insert(node->left, insert_node, count_small);
        } else {
            node->left = insert_node;
        }
    } else {
        count_small += node->count + 1;
        if (node->right) {
            BST_insert(node->right, insert_node, count_small);
        } else {
            node->right = insert_node;
        }
    }
}
class Solution {
public:
    vector<int> countSmaller(vector<int> &nums) {
        vector<int> result;
        vector<BSTNode *> node_vec;
        vector<int> count;
        for (int i = int(nums.size()) - 1; i >= 0; i--) {
            node_vec.push_back(new BSTNode(nums[i]));
        }
        count.push_back(0);
        for (int i = 1; i < node_vec.size(); i++) {
            int count_small = 0;
            BST_insert(node_vec[0], node_vec[i], count_small);
            count.push_back(count_small);
        }
        for (int i = int(node_vec.size()) - 1; i >= 0; i--) {
            delete node_vec[i];
            result.push_back(count[i]);
        }
        return result;
    }
};

int main() {
    int data[] = {6, 5, 4, 3, 2, 1};
    vector<int> v;
    v.reserve(6);
    for (int & i : data) {
        v.push_back(i);
    }
    v = Solution().countSmaller(v);
    for (auto &i : v) {
        cout << i << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值