美文网首页
2020-07-03 二分查找,二叉树,位运算,队列

2020-07-03 二分查找,二叉树,位运算,队列

作者: nowherespyfly | 来源:发表于2020-07-04 15:19 被阅读0次

53-I 在排序数组中查找数字 I

利用二分查找法,可以定位到第一个或者最后一个查找元素。
查找第一个时,如果中间元素小于或大于查找元素,都可以将区间缩小一半;而中间元素等于查找元素时,需要判断中间元素是不是第一个出现的元素,判断方式为查看前一个元素,如果前一个元素等于查找元素,第一个元素则出现在前一半区间中;如果前一个元素不等于查找元素,第一个元素就是当前元素,可以返回。
查找最后一个出现的元素位置也是类似的做法。

class Solution {
public:
    int search_first(vector<int> &nums, int target, int left, int right){
        if(left > right)
            return -1;
        int mid = left + (right - left) / 2;
        if(nums[mid] > target)
            return search_first(nums, target, left, mid - 1);
        if(nums[mid] < target)
            return search_first(nums, target, mid + 1, right);
        // nums[mid] == target
        // 这里利用了短路的做法
        if(mid == left || nums[mid - 1] != target)
            return mid;
        return search_first(nums, target, left, mid - 1);
    }
    int search_last(vector<int> &nums, int target, int left, int right){
        if(left > right)
            return -1;
        int mid = left + (right - left) / 2;
        if(nums[mid] > target)
            return search_last(nums, target, left, mid - 1);
        if(nums[mid] < target)
            return search_last(nums, target, mid + 1, right);
        // nums[mid] == target
        if(mid == right || nums[mid + 1] != target)
            return mid;
        return search_last(nums, target, mid + 1, right);
    }
    int search(vector<int>& nums, int target) {
        int first = search_first(nums, target, 0, nums.size() - 1);
        if(first < 0)
            return 0;
        int last = search_last(nums, target, 0, nums.size() - 1);
        return last - first + 1;
    }
};

53 - II. 0~n-1中缺失的数字

二分查找法,如果中间数字等于下标数,缺失的数字就在右半边;否则,查看左边的数字是不是等于下标,等于的话,中间下标就是缺失的数字,否则在左半边。
实现用了递归和迭代两种实现方法:
迭代

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0, right = nums.size();
        int mid, ret;
        while(left < right){
            mid = left + (right - left) / 2;
            if(nums[mid] == mid){
                left = mid + 1;
            }
            else{
                if(mid == 0 || (mid > 0 && nums[mid - 1] == mid - 1))
                    return mid;
                else
                    right = mid;
            }
        }
        return left;
    }
};

递归

public:
    int binary_search(vector<int>& nums, int left, int right){
        if(left >= right)
            return left;
        int mid = left + (right - left) / 2;
        if(nums[mid] == mid)
            return binary_search(nums, mid + 1, right);
        if(mid == 0 || (mid > 0 && nums[mid - 1] == mid - 1))
            return mid;
        return binary_search(nums, left, mid);
    }
    int missingNumber(vector<int>& nums) {
        return binary_search(nums, 0, nums.size());
    }
};

54 二叉搜索树的第k大节点

二叉搜索树的中序遍历是有序的。因此对二叉搜索树进行逆中序遍历,返回值为TreeNode指针,未找到第k个节点之前,返回空指针,找到后返回该节点指针。每次右孩子遍历完,将k减一,k减到1时,找到了第k大的节点。
这个题目的代码逻辑比较难写,需要重点学习下。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* kthLargestCore(TreeNode* root, int &k){
        if(root == nullptr)
            return nullptr;
        TreeNode* target = kthLargestCore(root -> right, k);
        if(target == nullptr){
            if(k == 1)
                target = root;
            k--;
        }
        if(target == nullptr)
            target = kthLargestCore(root -> left, k);
        return target;
    }
    int kthLargest(TreeNode* root, int k) {
        TreeNode* target = kthLargestCore(root, k);
        return target -> val;
    }
};

55-II 平衡二叉树

该问题可以用二叉树的后序遍历解决,首先判断左右子树分别是否平衡,如果左右子树平衡,再根据左右子树的高度,判断该树是否平衡,并且返回树高度。这样只需要遍历一次即可。
另一个技巧,如果有多个需要返回的值,可以将其中某些值的引用作为参数传回来。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isBalanced_Depth(TreeNode* root, int &depth){
        if(root == nullptr){
            depth = 0;
            return true;
        }
        int left_depth = 0, right_depth = 0;
        if(isBalanced_Depth(root -> left, left_depth) && isBalanced_Depth(root -> right, right_depth)){
            if(abs(left_depth - right_depth) <= 1){
                depth = max(left_depth, right_depth) + 1;
                return true;
            }
            else return false;
        }
        else return false;
    }
    bool isBalanced(TreeNode* root) {
        int depth = 0;
        return isBalanced_Depth(root, depth);
    }
};

56-I 数组中只出现一次的两个数字

这个题目有点难,如果只有一个数字只出现一次,那么我们可以利用同一个数异或两次是0,将所有数字进行异或操作,最后的结果就是只出现一次的数字。
如果有两个数字只出现一次,那么将所有数字异或一次,得到的结果是这两个数字的异或结果,这个数字一定不为0。找到这个数字中出现1的某一位,则这两个只出现一次的数中,一个是这一位等于1,一个是不等于1.通过这一位是否为1,可以将这两个数字分到两个数组中,之后,分别在这两个数组中进行异或,就可以得到这两个数字。

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int EORresult = 0;
        for(int i = 0; i < nums.size(); i++)
            EORresult ^= nums[i];
        // find first 1
        int k = 0;
        while(EORresult % 2 == 0){
            EORresult = EORresult >> 1;
            k++;
        }
        int check_mod = 1 << k;
        // divide nums into two parts
        int num1 = 0, num2 = 0;
        for(int i = 0; i < nums.size(); i++){
            if(nums[i] & check_mod)
                num1 ^= nums[i];
            else
                num2 ^= nums[i];
        }
        vector<int> ret = {num1, num2};
        return ret;
    }
};

56-II 数组中唯一只出现一次的数字

其他数字都出现3次,因此,这个题目就不能用异或来做了,但还是可以用位运算的思路。每个数字的每一位相加三次,这一位的和一定可以整除3.那么将所有数字的每一位求和,如果某一位的和可以整除3,那么这个只出现一次的数字在这一位上一定为0,否则为1。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int bit_sum[32] = {0};
        for(int i = 0; i < nums.size(); i++){
            int tmp = nums[i], j = 0;
            while(tmp){
                if(tmp & 1)
                    bit_sum[j]++;
                tmp = tmp >> 1;
                j++;
            }
        }
        for(int i = 0; i < 32; i++)
            bit_sum[i] = bit_sum[i] % 3;
        int num = 0;
        for(int i = 31; i >= 0; i--)
            num = (num << 1) + bit_sum[i];
        return num;
    }
};

57-II 和为s的连续正数序列

还是用双指针,left和right,当前和小于target时,right++,大雨target时,left++,等于时,记录下当前的序列。

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> ret;
        int left = 1, right = 2, sum = 3;
        while(2 * left < target){
            if(sum == target){
                vector<int> tmp;
                for(int i = left; i <= right; i++)
                    tmp.push_back(i);
                ret.push_back(tmp);
            }
            if(sum <= target){
                right++;
                sum += right;
            }
            else{
                sum -= left;
                left++;
            }
        }
        return ret;
    }
};

58 翻转单词顺序

先翻转整句话,再翻转每个单词。比较麻烦的就是空格的处理,所以开辟了额外空间,如果空格没问题,就可以原地翻转了。

class Solution {
public:
    string reverse_string(string s, int begin, int end){
        string rev_s = "";
        for(int i = end; i >= begin; i--)
            rev_s += s[i];
        return rev_s;
    }
    string reverseWords(string s) {
        // remove extra spaces
        s = reverse_string(s, 0, s.size() - 1);
        string ret_s = "";
        int begin = 0, end = 0, str_len = s.size();
        while(end < str_len){
            while(begin < str_len && s[begin] == ' ')
                begin++;
            if(begin == str_len)
                break;
            end = begin;
            while(end < str_len && s[end] != ' ')
                end++;
            if(ret_s.size())
                ret_s += ' ';
            ret_s += reverse_string(s, begin, end - 1);
            begin = end;
        }
        return ret_s;
    }
};

58-II 左旋转字符串

还是两次旋转,第一次旋转0-k,k-strlen,第二次旋转整个字符串。

class Solution {
public:
    void reverse_string(string &s, int begin, int end){
        while(begin < end){
            char tmp = s[begin];
            s[begin] = s[end];
            s[end] = tmp;
            begin++;
            end--;
        }
    }
    string reverseLeftWords(string s, int n) {
        reverse_string(s, 0, n - 1);
        reverse_string(s, n, s.size() - 1);
        reverse_string(s, 0, s.size() - 1);
        return s;
    }
};

59-I 滑动窗口的最大值

用双向队列,存放所有可能成为滑动窗口最大值的元素的下标。如果当前队列中的每个元素小于新加进来的元素,那么这个元素就不可能成为后面的滑动窗口的最大值,应该出队列,此时是从队尾出队列。另一方面,如果当前队列中某个元素的下标已经在滑动窗口之外,那么这个元素也应该出队列,此时从队首出队列。每次,当前最大元素就在队头。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> binary_queue;
        vector<int> maxes;
        for(int i = 0; i < nums.size(); i++){
            while(binary_queue.size() && binary_queue.front() + k <= i)
                binary_queue.pop_front();
            while(binary_queue.size() && nums[binary_queue.back()] < nums[i])
                binary_queue.pop_back();
            binary_queue.push_back(i);
            if(i >= k - 1)
                maxes.push_back(nums[binary_queue.front()]);
        }
        return maxes;
    }
};

59-II 队列的最大值

实现一个最大队列,pop,push,max都是常数时间。跟滑动窗口类似,用一个双向队列存放当前最大值。每次push的时候,双向队列中存入可能成为最大值的值,也就是说,如果队列中的某些元素比push进来的元素小,那么他们不可能成为最大值了,就应该先出队列。pop时,需要判断一下当前队头的元素是否要pop的元素,如果是,就出队列。
一个需要注意的地方是如果队列中有相同的元素,这两个元素都会进入双向队列,这样pop的时候不会出错。

class MaxQueue {
public:
    MaxQueue():q_size(0){}
    
    int max_value() {
        if(q_size == 0)
            return -1;
        return max_q.front();
    }
    
    void push_back(int value) {
        q.push(value);
        while(!max_q.empty() && max_q.back() < value)
            max_q.pop_back();
        max_q.push_back(value);
        q_size++;
    }
    
    int pop_front() {
        if(q_size == 0)
            return -1;
        int ret = q.front();
        q.pop();
        if(ret == max_q.front())
            max_q.pop_front();
        q_size--;
        return ret;
    }
private:
    deque<int> max_q;
    queue<int> q;
    int q_size;
};

相关文章

  • 2020-07-03 二分查找,二叉树,位运算,队列

    53-I 在排序数组中查找数字 I 利用二分查找法,可以定位到第一个或者最后一个查找元素。查找第一个时,如果中间元...

  • 想去阿里——这是你必备的实力

    算法和数据结构数组、链表、二叉树、队列、栈的各种操作(性能,场景) 二分查找和各种变种的二分查找 各类排序算法以及...

  • 想去阿里——这是你必备的实力

    算法和数据结构数组、链表、二叉树、队列、栈的各种操作(性能,场景)二分查找和各种变种的二分查找各类排序算法以及复杂...

  • java面试需要掌握的知识点

    基础知识: 算法和数据结构数组、链表、二叉树、队列、栈的各种操作(性能,场景)二分查找和各种变种的二分查找各类排序...

  • 数据结构和算法-持续更新中...

    栈 二叉树 队列 堆 排序 冒泡排序 归并排序 先分割一个再按照大小有序合并 基数排序 查找 二分查找(折半查找)...

  • Swift语言实现几个简单算法

    栈队列二分查找插入排序归并排序快速排序 栈 队列 二分查找 二分查找是用于快速查找到目标数据(已排序)的一种查找方...

  • iOSer必须了解的数据结构

    数据结构 :哈希表、堆、栈、队列、链表、二叉树 操作系统(iOS)的堆、栈 算法 :排序、冒泡、快排、二分查找 数...

  • 读书笔记:《算法图解》第一章 算法简介

    二分查找是对半查找,进队列表是有序时有效。 n个元素的列表,二分查找最多需要log2nlog2n 步,简单顺序查找...

  • c++ primer 阅读 day8

    3.4 迭代器介绍 使用迭代器运算二分查找 3.5 数组

  • 剑指offer python

    连续子数组最大和 二分查找 快排 二叉树的镜像 链表中环的入口结点 矩阵路径 两个栈实现队列 反转单链表 和为S的...

网友评论

      本文标题:2020-07-03 二分查找,二叉树,位运算,队列

      本文链接:https://www.haomeiwen.com/subject/rjcsqktx.html