面经算法题手撕补充

目录

Perfect Squares 完全平方数

合并K个链表(hard)

相同原理:最小区间原题

LRU Cache(双向链表+hash map)

查找兄弟单词

快速幂

找出字符数组中出现次数最多的字符

给你一个数组,求一个k值,使得前k个数的方差 + 后面n-k个数的方差最小 。

我手中有一堆扑克牌, 但是观众不知道它的顺序。

两个排序好的数组,找第k小的数字,要求logn的时间复杂度,二分搜索变形。

两个排好序的数组,找中位数。

蓄水池抽样:给出从n个数中随机选择1个的方法。注意,n非常大,并且一开始不知道其具体值。数字是一个一个给你的,当给完之后,你必须立刻给出随机的结果。

蓄水池采样应用:

链表随机数 Linked List Random Node

完全二叉树最后一个节点

字典序数字 Lexicographical Numbers

找字典序的第k个数 K-th Smallest in Lexicographical Order

最少移动次数01序列

最长AB子串

数组两两元素求异或,求最大的异或值(这题没来得及写)

最大全1矩形

全1连通块数目

最小覆盖子串

最长连续序列

LeetCode40 最小缺失正数


Perfect Squares 完全平方数

问题描述:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

Example 1:

Input: n = 12

Output: 3 
Explanation: 12 = 4 + 4 + 4.

Example 2:

Input: n = 13

Output: 2
Explanation: 13 = 4 + 9.

思路1:

dp

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0] = 0;
        // 从i = 0开始,第一遍可以把所有平方数最少次数标为1
        for(int i = 0;i < n;i++){
            for(int j = 1;i+j*j <= n;j++){
                dp[i+j*j] = min(dp[i+j*j],dp[i]+1);
            }
        }
        return dp[n];
    }
};

 

思路2:

根据四平方和定理,任意一个正整数均可表示为4个整数的平方和,其实是可以表示为4个以内的平方数之和,那么就是说返回结果只有 1,2,3 或4其中的一个

首先我们将数字化简一下,由于一个数如果含有因子4,那么我们可以把4都去掉,并不影响结果,比如2和8,3和12等等,返回的结果都相同,读者可自行举更多的栗子。

还有一个可以化简的地方就是,如果一个数除以8余7的话,那么肯定是由4个完全平方数组成,这里就不证明了,因为我也不会证明,读者可自行举例验证。

那么做完两步后,一个很大的数有可能就会变得很小了,大大减少了运算时间,下面我们就来尝试的将其拆为两个平方数之和,如果拆成功了那么就会返回1或2,因为其中一个平方数可能为0. (注:由于输入的n是正整数,所以不存在两个平方数均为0的情况)。注意下面的 !!a + !!b 这个表达式,可能很多人不太理解这个的意思,其实很简单,感叹号!表示逻辑取反,那么一个正整数逻辑取反为0,再取反为1,所以用两个感叹号!!的作用就是看a和b是否为正整数,都为正整数的话返回2,只有一个是正整数的话返回1

class Solution {
public:
    int numSquares(int n) {
        while(n % 4==0) n = n/4;
        if(n % 8 == 7) return 4;
        // a可以从0开始,n本身就是平方数情况
        for(int a = 0;a*a <= n;a++){
            int b = sqrt(n-a*a);
            if(a*a+b*b == n){
                return !!a+!!b;
            }
        }
        return 3;
    }
};

 

合并K个链表(hard)

Input:
[
  1->4->5,
  1->3->4,
  2->6
]
Output: 1->1->2->3->4->4->5->6

问题:

N个目标怎样最快速的两两合并

不管合并几个,基本还是要两两合并,两个先合并,合并好了再跟第三个,然后第四个直到第k个。这样的思路是对的,但是效率不高(时间复杂度为O(nk*nk)

思路:

 

使用堆:

把k个链表开头的值排个序,每次取最小的一个值放到答案链表中,这次取完之后更新这个值为它后面的一个值。接着这么取一直到全部取完。

那么每次更新之后怎么对当前这k个值重新排序以便知道当前最小的是谁呢?用优先队列(或者堆)来维护这k个值就好啦!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
struct cmp{
    bool operator()(ListNode* h1,ListNode* h2){
        return h1->val > h2->val;
    }
};

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return NULL;
        if(lists.size() < 2) return lists[0];

        priority_queue<ListNode*,vector<ListNode*>,cmp> q;
        ListNode* dummy_head = new ListNode(-1);
        ListNode* pre = dummy_head,*cur;

        for(auto &node:lists){
            if(node) q.push(node);
        }

        while(!q.empty()){
            cur = q.top(); q.pop();
            pre->next = cur;
            pre = pre->next;
            if(cur->next) q.push(cur->next);
        }
        return dummy_head->next;
    }
};

复杂度分析:

由于每个值都要取一次,一共取nk次。每次更新优先队列要logk的复杂度。所以总时间复杂度为O(nklogk);

空间复杂度为优先队列所占空间,为O(k)。

 

2.递归

将原链表分成两段,然后对每段调用递归函数,suppose返回的left和right已经合并好了,然后再对left和right进行合并

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return NULL;
        return mergeNLists(lists,0,lists.size()-1);
        
    }
    
    ListNode* mergeNLists(vector<ListNode*>& lists,int l,int r) {
        if(l > r) return NULL;
        if(l == r) return lists[l];
        if(l+1 == r) return merge(lists[l],lists[r]);
        else{
            int mid = (l+r)/2;
            ListNode* left = mergeNLists(lists,l,mid);
            ListNode* right = mergeNLists(lists,mid+1,r);
            return merge(left,right);
        } 
    }
    
    // 合并两个链表
    ListNode* merge(ListNode* h1,ListNode* h2){
        ListNode* dummy_head = new ListNode(-1);
        ListNode* cur = dummy_head;
        while(h1 && h2){
            if(h1->val < h2->val){
                cur->next = h1;
                h1=h1->next;
            }
            else{
                cur->next = h2;
                h2=h2->next;
            }
            cur=cur->next;
        }
        if(h1) cur->next = h1;
        if(h2) cur->next = h2;
        return dummy_head->next;
    }
};

复杂度:

时间复杂度:

每合并一次,需要nk时间复杂度

递归方法需要合并logk次

故总时间复杂度:nklogk

空间复杂度:

栈深度:logk

 

相同原理:最小区间原题

k个有序的数组,找到最小的区间范围使得这k个数组中,每个数组至少有一个数字在这个区间范围内。比如:

  • 数组1:[4, 10, 15, 24, 26]
  • 数组2:[0, 9, 12, 20]
  • 数组3:[5, 18, 22, 30]

最小的区间是[20, 24],这个区间包含了数组1中的24,数组2中的20,数组3中的22

思路:

用多路归并,每次比较每个数组k个的头组成的区间大小

 

LRU Cache(双向链表+hash map)

题意:

为LRU Cache设计一个数据结构,它支持两个操作:

(1)get(key):如果key在cache中,则返回对应的value值,否则返回-1

(2)set(key,value):如果key不在cache中,则将该(key,value)插入cache中(注意,如果cache已满,则必须把最近最久未使用的元素从cache中删除);如果key在cache中,则重置value的值。

思路:

数组:

用什么数据结构来实现LRU算法呢?可能大多数人都会想到:用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。

这种实现思路很简单,但是有什么缺陷呢?需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。

链表:

  那么有没有更好的实现办法呢?

  那就是利用双向链表和hashmap(双向使得删除的复杂度为O(1))

(1)插入新的数据项:

如果新数据项在链表中存在(一般称为命中),则把该节点移到链表尾部,且如果value值不同,修改对应value值

如果不存在,则新建一个节点,放到链表尾部

若缓存满了,则把链表第一个节点删除即可。

(2)访问数据时:

如果数据项在链表中存在,则把该节点移到链表尾部,否则返回-1。

这样一来在链表头部的节点就是最近最久未访问的数据项。

复杂度:均为O(1)

struct Node{
    int key,value;
    Node *pre,*next;
    Node(int x,int y){
        key = x;
        value = y;
        pre = NULL;
        next = NULL;
    }
};

class LRUCache {
public:
    LRUCache(int cap) {
        capacity = cap;
        // 双向链表
        // 头指向最不常使用节点(满时从头的next开始删除)
        // 尾的pre指向最常使用节点
        head->next = tail;
        tail->pre = head;
    }

    int get(int key) {
        // 没找到
        if(m.find(key) == m.end()){
            return -1;
        }
        // 找到
        Node *cur = get_cur(m[key]);
        move_to_tail(cur);
        return m[key]->value;
    }

    void put(int key, int value) {
        // 已经存在key,只是value的值需要变更
        if(m.find(key) != m.end()){
            m[key]->value = value;
            Node *cur = get_cur(m[key]);
            move_to_tail(cur);
            return;
        }
        // 已满,删除最不常使用节点(head指向节点)
        else if(m.size() >= capacity){
            m.erase(head->next->key);
            head->next = head->next->next;
            head->next->pre = head;
        }
        Node *new_node = new Node(key,value);
        m[key] = new_node;
        move_to_tail(new_node);
    }

private:
    int capacity;
    unordered_map<int,Node*> m;
    Node *head = new Node(-1,-1);
    Node *tail = new Node(-1,-1);

    // 将一个节点从双向链表摘下
    Node * get_cur(Node *cur){
        cur->pre->next = cur->next;
        cur->next->pre = cur->pre;
        return cur;
    }

    // 将摘下节点移到最末尾(表示它是最新的)
    void move_to_tail(Node *cur){
        cur->pre = tail->pre;
        cur->pre->next = cur;
        tail->pre = cur;
        cur->next = tail;
    }
};

 

查找兄弟单词

live, evil, veil 这些字母顺序不同的词定义为 brother words,

给定一个词典, 编程输出所有的 brother words(以字典序输出)。

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

bool isBrother(string s1,string s2){
    if(s1.size() != s2.size()) return false;
    if(s1 == s2) return false;
    sort(s1.begin(),s1.end());
    sort(s2.begin(),s2.end());
    return s1==s2;
}

int main(){
    int n;
    while(cin >> n){
        // 注意这些参数写在里面初始化,因为传参形式是循环调用
        int idx;
        string str,word,idx_word;
        vector<string> vs;
        for(int i = 0;i < n;i++){
            cin >> str;
            vs.push_back(str);
        }
        cin >> word;
        cin >> idx;

        // 字典排序,为了输出要求
        sort(vs.begin(),vs.end());

        // 逐个判断
        int count = 0;
        for(auto &s:vs){
            if(isBrother(s,word)){
                count++;
                if(count == idx){
                    idx_word = s;
                }
            }
        }
        if(!vs.empty()) cout << count << endl;
        if(count >= idx){
            cout << idx_word << endl;
        }
    }
    return 0;
}

 

快速幂

1.O(n)做法

double my_power(double x,int n){
    double res = 1;
    for(int i = 0;i < n;i++){
        res *= x;
    }
    return res;
}

2.O(logn)做法

double my_power(double x,int n){
    // remind从x的一次开始,不断翻倍,x的2,x的4,x的8,理由是每个n都可以被拆成二进制数
    double res = 1,remind = x;
    while(n){
        if(n & 1) res *= remind;
        n = n >> 1;
        remind *= remind;
    }
    return res;
}

 

找出字符数组中出现次数最多的字符

思路:

哈希表映射一次,计数

int findMost(vector<int> nums){
    if(nums.empty()) return -1;

    unordered_map<int,int> m;
    int max_count = 0,max_num;
    for(auto &n:nums){
        if(m.count(n)) m[n]++;
        else m[n] = 1;
    }
    for(auto iter = m.begin();iter != m.end();iter++){
        if(iter->second > max_count){
            max_count = iter->second;
            max_num = iter->first;
        }
    }
    return max_num;
}

 

给你一个数组,求一个k值,使得前k个数的方差 + 后面n-k个数的方差最小 。

思路:

如 nums = [1,3,2,4]

运用公式D(x) = E(x*x)-(E(X))^2

我们可以先从左向右求出各个子段和各子段平方和,然后再从右向左求出各个子段和和各子段平方和,运用公式我们只需要正反遍历数组两次,就可以求得结果。

 

手中有一堆扑克牌, 但是观众不知道它的顺序。

第一步, 我从牌顶拿出一张牌, 放到桌子上。

第二步, 我从牌顶再拿一张牌, 放在手上牌的底部。

第三步, 重复前两步的操作, 直到我手中所有的牌都放到了桌子上。

最后, 观众可以看到桌子上牌的顺序是:13\12\11\10\9\8\7\6\5\4\3\2\1

请问, 我刚开始拿在手里的牌的顺序是什么?请用一个完整的函数来实现这一过程

正向过程:          手                                     桌子

                        1->2->3->4

                             2->3->4                            1

                           3->4->2                                1

                            4->2                                     3->1

                             2->4                                    3->1

                              4                                       2->3->1

                                                                       4->2->3->1

最后观众看牌顺序是4231,因为最先放在桌子上的在牌底

算法思想在于:
从手到桌子上进行两步操作:

手中第一张放在桌子上
手中第一张放在牌底


所以如果逆向思维:
1.手中最后一张放在第一张(此步需要考虑手中的牌是否大于1)
2.桌子上拿一张放在手中第一张

反向操作:      手                                               桌子

                      4                                                2->3->1

                      2->4                                             3->1

                     4->2                                              3->1

                     3->4->2                                           1

                     2->3->4                                            1

                      1->2->3->4

 

在这里是否要使用数组来完成?

1、数组概念是一组连续的内存空间,存储相同的类型的数据,那么对于这么多的数据移动操作来说,数组需要不停的重新分配的新的内存空间,对于内存来说不是很友好

2、LinkedList的本质是一个双向链表,链表中对内存空间的连续性并没有要求,且中间结点的增加与删除,那效率优于数组,而且本身提供了pollFirst 和 pollLast方法

基于以上两点,链表更适合当前这个场景

代码实现思路:

写出上面的过程后,根据逆过程去看怎样还原,这里我用了双端队列来做

deque<int> get_hand_pocker(vector<int> pockers){
    deque<int> hand_pockers;
    int cur;
    for(auto &pocker:pockers){
        //如果大于一张,则把手牌的最后一张放在最上面
        if(hand_pockers.size() > 1){
            cur = hand_pockers.back();
            hand_pockers.pop_back();
            hand_pockers.push_front(cur);
        }
        //从桌子上拿一张牌放在手上
        hand_pockers.push_front(pocker);
    }
    return hand_pockers;
}


int main(){
    vector<int> num = {4,2,3,1};
    deque<int> res = get_hand_pocker(num);
    for(auto &n:res) cout << n ;
    cout << endl;
}

 

两个排序好的数组,找第k小的数字,要求logn的时间复杂度,二分搜索变形。

两个排好序的数组,找中位数。

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(),n = nums2.size();
        return (findKth(nums1,0,nums2,0,(m+n+1)/2)+findKth(nums1,0,nums2,0,(m+n+2)/2))/2.0;
    }
    
    int findKth(vector<int>& nums1,int i, vector<int>& nums2,int j,int k){
        if(i >= nums1.size()) return nums2[j+k-1];
        if(j >= nums2.size()) return nums1[i+k-1];
        
        if(k == 1) return min(nums1[i],nums2[j]);
        
        int mid1,mid2;
        if(k/2 > nums1.size()) mid1 = INT_MAX;
        else mid1 = nums1[i+k/2-1];
        if(k/2 > nums2.size()) mid2 = INT_MAX;
        else mid2 = nums2[j+k/2-1];
        
        if(mid1 > mid2) j = j+k/2;
        else i=i+k/2;
        k -= k/2;
        return findKth(nums1,i,nums2,j,k);
    }
};

 

 

蓄水池抽样:给出从n个数中随机选择1个的方法。注意,n非常大,并且一开始不知道其具体值数字是一个一个给你的,当给完之后,你必须立刻给出随机的结果。

由于n非常大并且需要立即给出答案,因此无法把所有数字都保存起来然后进行随机。

再者n的具体值未知,因此任意时刻都需要有当前的结果。

 

于是第1个数字肯定选。那么问题就变成了当第i个数字到来时,是保留当前数字还是选择第i个数字的问题,i=2,3,...,n。此过程必须保证截止目前为止所见到的数字被选中的概率都相等。

假设当第i个数字到来时,前i-1个数字被选中的概率相等,此时只需要第i个数字被选中的概率为1/i即可保证当前的i个数字被选中的概率相等。

 

证明:

代码实现:

 

蓄水池采样应用:

链表随机数 Linked List Random Node

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
    Solution(ListNode* head) {
        this->head = head;
    }
    
    /** Returns a random node's value. */
    // 蓄水池采样,每次以1/len的概率替换res为当前数
    int getRandom() {
        int res = head->val,len = 2;
        ListNode* cur = head->next;
        while(cur){
            int i = rand() % len;
            if(i == 0) res = cur->val;
            len++;
            cur = cur->next;
        }
        return res;
    }
    
private:
    ListNode* head;
};

 

完全二叉树最后一个节点

完全二叉树和满二叉树Full Binary Tree的唯一区别是,完全二叉树最后一层的节点不满,而且假设最后一层有节点,都是从左边开始。 这样我们可以利用这个性质得到下面两个结论:

  1. 假如左子树高度等于右子树高度,则右子树为完全二叉树,左子树为满二叉树。
  2. 假如高度不等,则左子树为完全二叉树,右子树为满二叉树。
  3. 高度的时候只往左子树来找

Time Complexity - O(logn * logn), Space Complexity - O(1) (不考虑递归栈)

/**
 * 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* getLastNode(TreeNode* root) {
        if(!root) return NULL;
        TreeNode* cur = root;
        int left,right;
        // 二分查找
        while(cur){
            if(!cur->left && !cur->right) return cur;
            left = get_height(cur->left);
            right = get_height(cur->right);
            // 左子树为满二叉树,最后节点在右子树
            if(left == right) cur = cur->right;
            // 最后节点在左子树
            else cur = cur->left;
        }
        return NULL;
    }
    
    int get_height(TreeNode* root){
        TreeNode* cur = root;
        int level = 0;
        while(cur){
            level++;
            cur = cur->left;
        }
        return level;
    }
};

 

字典序数字 Lexicographical Numbers

Given an integer n, return 1 - n in lexicographical order.

For example, given 13, return: [1,10,11,12,13,2,3,4,5,6,7,8,9].

Please optimize your algorithm to use less time and space. The input size may be as large as 5,000,000.

以780为例

1,10,100,101...109,11,110,111...119,12,120,121..129...199

class Solution {
public:
    vector<int> lexicalOrder(int n) {
        vector<int> res(n);
        int cur = 1;
        // 顺序生成
        for(int i = 0;i < n;i++){
            res[i] = cur;
            if(cur*10 <= n) cur *= 10;
            else{
                if(cur >= n) cur /= 10;
                cur += 1;
                while (cur % 10 == 0) cur /= 10;
            }
        }
        return res;
    }
};

 

找字典序的第k个数 K-th Smallest in Lexicographical Order

其实这是个十叉树Denary Tree,就是每个节点的子节点可以有十个,比如数字1的子节点就是10到19,数字10的子节点可以是100到109,但是由于n大小的限制,构成的并不是一个满十叉树。

我们分析题目中给的例子可以知道,数字1的子节点有4个(10,11,12,13),而后面的数字2到9都没有子节点,那么这道题实际上就变成了一个先序遍历十叉树的问题,那么难点就变成了如何计算出每个节点的子节点的个数,我们不停的用k减去子节点的个数,当k减到0的时候,当前位置的数字即为所求

现在我们来看如何求子节点个数,比如数字1和数字2,我们要求按字典遍历顺序从1到2需要经过多少个数字,首先把1本身这一个数字加到step中,然后我们把范围扩大十倍,范围变成10到20之前,但是由于我们要考虑n的大小,由于n为13,所以只有4个子节点,这样我们就知道从数字1遍历到数字2需要经过5个数字,然后我们看step是否小于等于k,如果是,我们cur自增1,k减去step;如果不是,说明要求的数字在子节点中,我们此时cur乘以10,k自减1,以此类推,直到k为0推出循环,此时cur即为所求

class Solution {
public:
    int findKthNumber(int n, int k) {
        int cur = 1;
        --k;
        while(k > 0){
            long long step = 0,first = cur,last = cur+1;
            // 遍历1开头树,2开头树。。。
            while(fisrt < n){
                step += min((long long)n+1,last)-first;
                first *= 10;
                last *= 10;
            }
            // 1开头树数量小于k
            if(step <= k){
                ++cur;
                k -= step;
            } 
            // 否则,在树内,找10开头的数
            else{
                cur *= 10;
                --k;
            }
        }
    }
};

 

最少移动次数01序列

问题描述:

给定一个01序列串,现在需要将这个字符串改为“非递减“有序序列,请问其最小交换次数(任意两个位置可互换)?

分析:此题要求将01串按照非递减顺序排序的最小交换次数,只能是在需要交换的时候才交换,也就是将所有0移到左边,所有1放到右边所需的最小交换次数。可以利用双指针来处理。

 

最长AB子串

给定一个数组,数组中只包含0和1。请找到一个最长的子序列,其中0和1的数量是相同的。
例1:10101010 结果就是其本身。
例2:1101000 结果是110100
请大家展开自己的思路。

解:
这个题目,看起来比较简单,一些同学可能认为题目的描述符合动态规划的特征,然后就开始用动态规划解,努力找状态转移方程。这些同学的感觉,是很正确的。但,找状态转移方程,我们要对原来的数组进行变换一下。

原来是0和1的串,我们将0都换为-1。这样题目目标就变成,找到一个最长的子串,子串数字和是0。设原数组为A, DP[i]表示从0开始到i的子数组和。DP遍历一遍数组即可。例1中的数组产生的DP为:

0    1    2    3    4    5    6    7
1    0    1    0    1    0    1    0
这个例子,最后一个值是0,并且长度是偶数位。直接满足了结果。

再看例子2:

0    1    2    3    4    5    6
1    2    1    2    1    0    -1
5的位置为0,最长子串从0开始到5,长度为6。

上面这两个例子,所求的子串都是从头开始,如果不是从头开始,会是什么样的呢?看这个例子:1101100

0    1    2    3    4    5    6
1    2    1    2    3    2    1
通过观察上面的表格,我们可以得到,DP[0]==DP[6]==DP[2],DP[1]==DP[3]. 根据DP的定义,如果DP[i]==DP[j],i 一种方法,我们用map保存DP的值到位置的映射,如下表:

我们最终的算法,要综合考虑最常穿是否从头开始的。 上面的这个思路,时间复杂度是O(n),空间复杂度也是O(n).

class Solution:
    """
    @param S: a String consists of a and b
    @return: the longest of the longest string that meets the condition
    """

    def getAns(self, S):
        # Write your code here
        ans = 0
        #定义一个字典,key是AB之间的差距,val是在字符对应坐标
        D = {0: -1}
        if not S:
            return ans
    
        cnt = 0
        for i in range(len(S)):
            if S[i] == 'A':
                cnt += 1
            else:
                cnt -= 1
            #当cnt在字典中的时候,证明该情况出现过,i - D[cnt]之间的字符,AB的数量是相等的,
            if cnt in D:
                ans = max(ans, i - D[cnt])
            else:
                D[cnt] = i
    
        return ans

拓展:

升级版abc三个字符咋办,还是on,思考了6分钟我说思路,延续前面思路,要按(ab,ac)的差值来哈希和查找,是不是On我还得思考下细节

 

数组两两元素求异或,求最大的异或值(这题没来得及写)

用 Trie 来做,首先把所有的数的二进制存到 Trie 里面去,然后对于数组中的每个数 x,和 x 一起异或结果最大的 y 就是用 x 的二进制的反码在Trie 里面搜索尽可能的与 x 的反码匹配,这样当走到叶子节点时,叶子节点对应的数就是 y。然后遍历一遍数组,求出 max(x ^ y)

class Solution(object):
    def findMaximumXOR(self, nums):
        Trie = {}
        for x in nums:
            cur = Trie
            for bit in format(x, '032b'):
                if bit not in cur:
                    cur[bit] = {}
                cur = cur[bit]
            cur[''] = x
             
        def matchMaxXOR(x):
            cur = Trie
            for bit in format(x, '032b'):
                rev = '0' if bit == '1' else '1'
                if rev in cur:
                    cur = cur[rev]
                else:
                    cur = cur[bit]
            return cur['']
         
        res = 0
        for x in nums:
            res = max(res, x ^ matchMaxXOR(x))
         
        return res

 

最大全1矩形

Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing only 1's and return its area.

Example:

Input:
[
  ["1","0","1","0","0"],
  ["1","0","1","1","1"],
  ["1","1","1","1","1"],
  ["1","0","0","1","0"]
]
Output: 6

思路:这道题的二维矩阵每一层向上都可以看做一个直方图,输入矩阵有多少行,就可以形成多少个直方图,对每个直方图都调用 Largest Rectangle in Histogram 直方图中最大的矩形 中的方法,就可以得到最大的矩形面积。

(1)那么这道题唯一要做的就是将每一层构成直方图,由于题目限定了输入矩阵的字符只有 '0' 和 '1' 两种,所以处理起来也相对简单。方法是,对于每一个点,如果是‘0’,则赋0,如果是 ‘1’,就赋 之前的height值加上1。

(2)对每一行调用计算最大直方图

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        if(matrix.empty() || matrix[0].empty()) return 0;
        
        int n = matrix.size(),m = matrix[0].size(),max_rec = 0;
        vector<vector<int>> height(n,vector<int>(m));
        // 计算高度
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(matrix[i][j] == '1'){
                    height[i][j] = i == 0? 1:height[i-1][j]+1;
                }
                else height[i][j] = 0;
            }
        }
        // 每一行都可以调用计算直方图的最大矩形
        for(int i = 0;i < n;i++){
            max_rec = max(max_rec,getMaxRectangle(height[i]));
        }
        return max_rec;
    }
    
    int getMaxRectangle(vector<int> row){
        int n = row.size();
        int max_rec = 0,minH;
        for(int i = 0;i < n;i++){
            while(i < n-1 && row[i+1] >= row[i]) i++;
            minH = row[i];
            for(int j = i;j >= 0;j--){
                minH = min(row[j],minH);
                max_rec = max(max_rec,minH*(i-j+1));
            }
        }
        return max_rec;
    }
};

 

全1连通块数目

Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example 1:

Input:
11110
11010
11000
00000

Output: 1

Example 2:

Input:
11000
11000
00100
00011

Output: 3

思路:DFS或者BFS都可以实现

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        if(grid.empty() || grid[0].empty()) return 0;
        int m = grid.size(),n = grid[0].size(),cnt = 0;
        vector<vector<bool>> visited(m,vector<bool>(n,false));
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(grid[i][j] == '1' && visited[i][j] == false) {
                    cnt++;
                    bfs(i,j,grid,visited);
                }
            }
        }
        return cnt;
    }
    
    void bfs(int i,int j,vector<vector<char>>& grid,vector<vector<bool>> &visited){
        if(i < grid.size() && j < grid[0].size() && grid[i][j] == '1' && visited[i][j] == false){
            visited[i][j] = true;
            bfs(i-1,j,grid,visited);
            bfs(i+1,j,grid,visited);
            bfs(i,j-1,grid,visited);
            bfs(i,j+1,grid,visited);
        }
    }
};

 

最小覆盖子串

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:

如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

思路:

这题要注意T里面的字符可能有多次出现,不能用set而要用map去存

class Solution {
public:
    string minWindow(string s, string t) {
        string res;
        if(s.empty() || t.empty() || s.size() < t.size()) return res;
        // 使用字典存而不是set,因为t可能内部字符有重复
        unordered_map<char,int> word_map;
        int left = 0,cnt = 0,min_len = INT_MAX;
        for(auto &w:t) word_map[w]++;
        
        for(int i = 0;i < s.size();i++){
            if(word_map[s[i]] > 0) cnt++;
            word_map[s[i]]--;
            while(cnt == t.size()){
                if(min_len > i-left+1){
                    min_len = i-left+1;
                    // substr[begin,seq_len]注意!!!不是[begin,end]
                    res = s.substr(left,min_len);
                }
                if(++word_map[s[left]] > 0) cnt--;
                left++;
            }
        }
        return res;
    }
};

 

最长连续序列

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.

Your algorithm should run in O(n) complexity.

Example:

Input: [100, 4, 200, 1, 3, 2]
Output: 4
Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4.

思路:时间要求在O(1),所以必须用额外的空间换时间

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if(nums.size() < 2) return nums.size();
        unordered_set<int> num_set(nums.begin(),nums.end());
        int max_len = 0;
        for(auto val:nums){
            if(!num_set.count(val)) continue;
            num_set.erase(val);
            int left = val-1,right = val+1;
            // 如果能找到左右界,持续往前往后找,直到把这个连续串穷举完
            while(num_set.find(left)) num_set.erase(left--);
            while(num_set.find(right) num_set.erase(right++);
            max_len = max(max_len,right-left-1);
        }
        return max_len;
    }
};

 

LeetCode40 最小缺失正数

Given an unsorted integer array, find the smallest missing positive integer.

Example 1:

Input: [1,2,0]
Output: 3

Example 2:

Input: [3,4,-1,1]
Output: 2

Example 3:

Input: [7,8,9,11,12]
Output: 1

Note:

Your algorithm should run in O(n) time and uses constant extra space.

思路:事实上,缺失的最小正数,肯定在1到n+1之间,故这里用了下标信息

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        if(nums.empty()) return 1;
        // 事实上,缺失的最小正数,肯定在1到n+1之间
        int n = nums.size(),miss = n+1;
        // 把每个数如果能归到它对应的顺序位置,之间归过去
        for(int i = 0;i < n;i++){
            if(nums[i] > 0 && nums[i] < n && nums[nums[i]-1] != nums[i]){
                swap(nums[i],nums[nums[i]-1]);
                i--;
            }
        }
        // 第一个没有放对应正数的,即为缺失正数
        for(int i = 0;i < n;i++){
            if(nums[i] != i+1){
                miss = i+1;
                break;
            } 
        }
        return miss;
    }
};

 

 

(0-1背包)--- 改变一组数的正负号使得它们的和为一给定数

You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol.

Find out how many ways to assign symbols to make sum of integers equal to target S.

Example 1:

Input: nums is [1, 1, 1, 1, 1], S is 3. 
Output: 5
Explanation: 

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

There are 5 ways to assign symbols to make the sum of nums be target 3.

思路:

sum(P) - sum(N) = target
sum(P) + sum(N) = sum

  于是有sum(P) = (target + sum) / 2

那么不妨这样理解题意,从一个数组中选定一些数,使它们的和为sum(P),如此就变成了很经典的0/1背包问题,从一个n大小的背包中选出总和为sum(P)的方案个数。

  状态表示:dp[i] [j]代表前i个数中和为j的方案个数。
  状态转移方程:dp [i] [j] = dp[i-1] [j] + dp[i-1] [j-nums[i]],dp [0] [0] = 1
  返回结果:dp[n] [target],n为数组大小,target为sum(P)。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        if(nums.empty()) return 0;
        int sum = 0;
        for(auto n:nums) sum += n;
        if((sum < S || (sum+S)%2)) return 0;
        int target = (sum+S)/2,n = nums.size();
        vector<vector<int>> dp(target+1,vector<int>(n+1,0));
        
        dp[0][0] = 1;
        for(int i = 0;i <= target;i++){
            for(int j = 1;j <= n;j++){
                int cur = nums[j-1];
                dp[i][j] = dp[i][j-1];
                if(cur <= i){
                    dp[i][j] += dp[i-cur][j-1];
                }
            }
        }
        
        return dp[target][n];
    }
};

空间优化:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        
        if(nums.empty()) return 0;
        int sum = 0;
        for(auto n:nums) sum += n;
        if((sum < S || (sum+S)%2)) return 0;
        int target = (sum+S)/2,n = nums.size();
        vector<int> dp(target+1,0);
        
        dp[0] = 1;
        for(auto num:nums){
            for(int i = target;i >= num;i--){
                dp[i] = dp[i] + dp[i-num];
            }
        }
        
        return dp[target];
    }
};

 

最大时间

Given an array of 4 digits, return the largest 24 hour time that can be made.

The smallest 24 hour time is 00:00, and the largest is 23:59.  Starting from 00:00, a time is larger if more time has elapsed since midnight.

Return the answer as a string of length 5.  If no valid time can be made, return an empty string.

Example 1:

Input: [1,2,3,4]
Output: "23:41"

Example 2:

Input: [5,5,5,5]
Output: ""

思路:

不要用规则去贪心枚举!!!会有问题!!!

反例:0,2,6,6

规则枚举会产生20:然后后面非法,就会返回空,事实上会有06:26

这题应该用dfs去枚举,一共才4! = 24种情况

class Solution {
public:
    vector<string> possible_res; 
    string largestTimeFromDigits(vector<int>& A) {
        vector<bool> visited(4,false);
        string res;
        dfs(A,visited,"");
        if(possible_res.empty()) return res;
        sort(possible_res.begin(),possible_res.end());
        res = possible_res[possible_res.size()-1];
        res.insert(2,":");
        return res;
    }
    
    // 暴力枚举
    void dfs(vector<int>& A,vector<bool>& visited,string out){
        if(out.size() == 4){
            if(isValid(out)) possible_res.push_back(out);
            return;
        }
        for(int i = 0;i < 4;i++){
            if(!visited[i]){
                visited[i] = true;
                dfs(A,visited,out + to_string(A[i]));
                visited[i] = false;
            }
        }
    }
    
    // 判断是否合法
    bool isValid(string s){
        string s1 = s.substr(0,2);
        string s2 = s.substr(2,2);
        if(s1 >= "24" || s2 >= "60") return false;
        return true;
    }
};

 

最长连续线段

https://blog.csdn.net/starstar1992/article/details/65745533

 

射气球(原理同上)

There are a number of spherical balloons spread in two-dimensional space. For each balloon, provided input is the start and end coordinates of the horizontal diameter. Since it's horizontal, y-coordinates don't matter and hence the x-coordinates of start and end of the diameter suffice. Start is always smaller than end. There will be at most 104 balloons.

An arrow can be shot up exactly vertically from different points along the x-axis. A balloon with xstart and xend bursts by an arrow shot at x if xstart ≤ x ≤ xend. There is no limit to the number of arrows that can be shot. An arrow once shot keeps travelling up infinitely. The problem is to find the minimum number of arrows that must be shot to burst all balloons.

Example:

Input:
[[10,16], [2,8], [1,6], [7,12]]

Output:
2

Explanation:
One way is to shoot one arrow for example at x = 6 (bursting the balloons [2,8] and [1,6]) and another arrow at x = 11 (bursting the other two balloons).

思路:先给线段排序,然后O(n)遍历就可以了

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if(points.size() < 2) return points.size();
        // pair排序默认就是按照第一个元素排的
        sort(points.begin(),points.end());
        int cnt = 1,first = points[0][0],second = points[0][1];
        for(int i = 1;i < points.size();i++){
            if(points[i][0] <= second){
                first = max(first,points[i][0]);
                second = min(second,points[i][1]);
            }else{
                cnt++;
                first = points[i][0];
                second = points[i][1];
            }
        }
        return cnt;
    }
};

 

一个链表,奇数位升序,偶数位降序,将其转化成完全升序的链表

直接拆分+反转+归并

 

O(1)空间判断整数是否为回文

class Solution {
public:
    bool isPalindrome(int x) {
        // 负数和末尾有0直接就不是回文
        if(x < 0) return false;
        if(x == 0) return true;
        if(x % 10 == 0) return false;
        
        // 拆分两边,判断回文
        int half = 0;
        while(x > half){
            half = half * 10 + x % 10;
            x /= 10;
        }
        if(half == x) return true;
        half /= 10;
        return half == x;
    }
};

 

有序链表转二叉搜索树

思路:

快慢指针找到中点作为根节点,然后递归

class Solution {
public:
    TreeNode* sortedListToBST(ListNode* head) {
        if(!head) return NULL;
        
        // 找到中点
        ListNode* fast = head,*slow = head,*pre;
        while(fast && fast->next){
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        
        TreeNode* root = new TreeNode(slow->val);
        // 只有一个节点情况,直接返回
        if(!head->next) return root;

        // 否则就递归
        pre->next = NULL;
        root->left = sortedListToBST(head);
        root->right = sortedListToBST(slow->next);
        
        return root;
    }
};

 

logN解法

def getSum(x,n):
	if n == 1:
		return x,x
	if n % 2 == 0:
		pre_sum,last = getSum(x,n/2)
		sum = pre_sum + pre_sum *last
		last = last*last
	else:
		pre_sum,last = getSum(x,n-1)
		last = last*x
		sum = pre_sum + last
	return sum,last

print(getSum(2,10)[0])
		

 

给定一个数组,问是否能把这个数组一份为二,且两个子数组和相同

https://leetcode.com/problems/partition-equal-subset-sum/

思路:

数组中和为sum/2的组合数有多少种

这里不需要dfs,因为只是种类数,这是一个典型的01背包题

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int m = nums.size();
        if(m == 0) return true;
        int sum = 0;
        for(auto n:nums) sum += n;
        if(sum&1) return false;
        sum /= 2;
        vector<vector<int>> dp(sum+1,vector<int>(m+1,0));
        dp[0][0] = 1;
        for(int i = 1;i <= sum;i++){
            for(int j = 1;j <= m;j++){
                if(nums[j-1] <= i){
                    dp[i][j] = max(dp[i-nums[j-1]][j-1],dp[i][j-1]);
                }else{
                    dp[i][j] = dp[i][j-1];
                }
            }
        }
        return dp[sum][m] > 0;
    }
};

 

判断合法ip地址

Given a string containing only digits, restore it by returning all possible valid IP address combinations.

Example:

Input: "25525511135"
Output: ["255.255.11.135", "255.255.111.35"]
class Solution(object):
    def __init__(self):
        self.res = []
        
    def restoreIpAddresses(self, s):
        """
        :type s: str
        :rtype: List[str]
        """
        self.dfs(s,[],0)
        return self.res
    
    def dfs(self,s,out,begin):
        if len(out) == 4:
            if begin == len(s):
                out = '.'.join(out)
                self.res.append(out)
            return
        
        if begin >= len(s):
            return
        
        # 注意对0的特殊处理,因为01这种是非法的
        if s[begin] == '0':
            out.append('0')
            self.dfs(s,out,begin+1)
        else:
            for i in range(1,4):
                cur = s[begin:begin+i]
                if int(cur) >= 1 and int(cur) <= 255:
                    self.dfs(s,out+[cur],begin+i)
        

 

中文字符串,比如一千五百亿八千九百万六百二十这种形式,转换成long long的整数,要我考虑很多非法输入

用栈来实现算法,N表示当前的数,如果数字则直接入栈;如果是百千万亿这种单位,就把栈中比N小的数弹出累加之后的和,乘上N再入栈。 
最后把栈中元素累加就得出结果了。 

如果不用栈也可以做,要记录下最近最大的单位是多少,才能保存前面的累加和。如果一亿三千二百万,当扫描到万的时候,要知道前面是三千二百,千的单位比万小的情况,所以要把累加和变成 ” 三千二百 * 万 ”,而不能只是 “二百 * 万”,也不能是 ”一亿三千二百 * 万”

def getNum(line):
    stack = []
    val1 = '零一二三四五六七八九'
    val2 = '十百千万亿'
    bases = [10,100,1000,10000,100000000]

    for s in line:
        # 数字
        if s in val1:
            stack.append(val1.index(s))
        # 不是数字
        else:
            s = val2.index(s)
            base = bases[s]
            sum = 0
            #
            while len(stack) > 0 and stack[-1] < base:
                cur = stack.pop()
                sum += cur
            if sum > 0:
                sum *= base
            else:
                sum = base
            stack.append(sum)

    sum = 0
    for cur in stack:
        sum += cur
    return sum


if __name__ == '__main__':
    line = '一亿二千三百八十万九千'
    res = getNum(line)
    print(res)

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值