Category Archives: LeetCode

LeetCode Gas Station

134. Gas Station

There are N gas stations along a circular route, where the amount of gas at station i is gas[i].

You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.

Return the starting gas station’s index if you can travel around the circuit once in the clockwise direction, otherwise return -1.

Note:

  • If there exists a solution, it is guaranteed to be unique.
  • Both input arrays are non-empty and have the same length.
  • Each element in the input arrays is a non-negative integer.

Example 1:

Input: 
gas  = [1,2,3,4,5]
cost = [3,4,5,1,2]

Output: 3

Explanation:
Start at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
Travel to station 4. Your tank = 4 - 1 + 5 = 8
Travel to station 0. Your tank = 8 - 2 + 1 = 7
Travel to station 1. Your tank = 7 - 3 + 2 = 6
Travel to station 2. Your tank = 6 - 4 + 3 = 5
Travel to station 3. The cost is 5. Your gas is just enough to travel back to station 3.
Therefore, return 3 as the starting index.

Example 2:

Input: 
gas  = [2,3,4]
cost = [3,4,3]

Output: -1

Explanation:
You can't start at station 0 or 1, as there is not enough gas to travel to the next station.
Let's start at station 2 and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
Travel to station 0. Your tank = 4 - 3 + 2 = 3
Travel to station 1. Your tank = 3 - 3 + 3 = 3
You cannot travel back to station 2, as it requires 4 unit of gas but you only have 3.
Therefore, you can't travel around the circuit once no matter where you start.

给定一个圆环,环上有n个加油站,每个加油站有gas[i]升油,一辆车从第i个加油站走到第i+1个加油站需要耗费cost[i]升油。问该车能否绕行圆环一周,如果能给出起点。

  1. 对于某个点i,如果gas[i]-cost[i]>=0,则车能从i走到i+1。
  2. 如果从A出发,无法到达B(但能到达B-1),则所有在A、B之间的起点都无法到达B。反证法,想象一下,如果A,B之间有一起点C能到达B,则说明C到B之间总的gas大于中的cost,又因为C在A,B之间,A能到达C,说明A到C之间中的gas大于cost。那么,既然A能到C,C能到B,则A能到B,和前提假设矛盾,所以原命题成立。
  3. 如果圆环上中的gas大于总的cost,则一定有一个解。
  4. 假设解的起点为i,则从i肯定能到达环上的任意一点。

基于以上的讨论,本题的解法是这样的:

  1. 从0开始走,如果能到达i,但无法到达i+1,则所有0~i的点都不是起点,因为根据讨论2,0~i的点都无法到达i+1,而根据讨论4,0~i的点不可能是解。
  2. 所以尝试起点为i+1,继续往前走。
  3. 最后,如果满足3,因为其他所有点都不可能是正确答案,且根据题目,解是唯一的,所以上述过程找到的起点就是正确解。

完整代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
    {
        int gasSum = 0, costSum = 0, tank = 0, start = 0;
        for (int i = 0; i < gas.size(); ++i) {
            gasSum += gas[i];
            costSum += cost[i];
            tank += (gas[i] – cost[i]);
            if (tank < 0) {
                start = i + 1;
                tank = 0;
            }
        }
        if (gasSum < costSum)
            return -1;
        else
            return start;
    }
};

本代码提交AC,用时6MS。

LeetCode Word Break

139. Word Break

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

Note:

  • The same word in the dictionary may be reused multiple times in the segmentation.
  • You may assume the dictionary does not contain duplicate words.

Example 1:

Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: Return true because "leetcode" can be segmented as "leet code".

Example 2:

Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
             Note that you are allowed to reuse a dictionary word.

Example 3:

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false

给定一个词典和一个字符串,问字符串能否拆分成若干个词典中有的词。比如样例中词典中有leet和code,则给定的字符串leetcode就能拆分成leet和code,他们都在词典中。
使用DP思路。假设dp[j]表示字符串下标j之前的字符串能否拆分成词典中的词的组合。如果要求dp[i],则我们可以看看i之前的所有j,如果有dp[j]是true的,且[j,i)的子串在dict中,说明[0,i)也能被拆分成词典中的词,所以dp[i]=true。最后我们只需要返回dp[n]即可。
对于s=”leetcode”,dp=1,0,0,0,1,0,0,0,1,所以dp[n]=1。
代码如下:

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict)
    {
        unordered_set<string> dict(wordDict.begin(), wordDict.end());
        int n = s.size();
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        for (int i = 1; i <= n; ++i) {
            for (int j = i – 1; j >= 0; –j) {
                if (dp[j] && dict.find(s.substr(j, i – j)) != dict.end()) {
                    dp[i] = 1;
                    break;
                }
            }
        }
        return dp[n];
    }
};

本代码提交AC,用时3MS。

LeetCode Task Scheduler

LeetCode Task Scheduler Given a char array representing tasks CPU need to do. It contains capital letters A to Z where different letters represent different tasks.Tasks could be done without original order. Each task could be done in one interval. For each interval, CPU could finish one task or just be idle. However, there is a non-negative cooling interval n that means between two same tasks, there must be at least n intervals that CPU are doing different tasks or just be idle. You need to return the least number of intervals the CPU will take to finish all the given tasks. Example 1:

Input: tasks = ['A','A','A','B','B','B'], n = 2
Output: 8
Explanation: A -> B -> idle -> A -> B -> idle -> A -> B.
Note:
  1. The number of tasks is in the range [1, 10000].

CPU需要处理一系列任务,限制条件是两个相同的任务被处理的时间至少需要间隔n时刻,问CPU最少需要多长时间能处理完所有任务。 比赛没做出来,参考讨论区。 根据题意,两个任务被处理至少需要间隔n时刻,所以我们可以认为CPU处理一批任务的循环周期是n+1,比如0时刻处理了任务’A’,则至少要到n+1时刻才能再次处理任务’A’,中间间隔了n时刻。 假设数量最多的任务的数量是k,则我们至少需要k个周期才能把这个任务处理完。为了让CPU处理的空闲时间更少,我们在一个周期内尽量让CPU处理的任务更丰富。所以想象一下,我们有k个桶,相当于k个周期,每个周期,我们把频率最高的任务拿出来,分发到这最多k个桶中。如果所有不同任务都分发完了还没有填满一个桶,则说明该桶内(周期内)CPU需要空闲等待。 比如样例中,最高频的任务是A和B,都是3,所以我们至少需要3个桶。每个桶的容量是n+1=3,相当于相同任务的距离是3。每次我们把A和B扔到不同的桶中,前两个桶各有一个空闲等待,第三个桶因为结束了,所以不需等待。 因为每次都需要取出最高频的任务去分发,所以用一个优先队列来实时更新频率排名。 完整代码如下: [cpp] class Solution { public: int leastInterval(vector<char>& tasks, int n) { map<char, int> count; for (const auto& c : tasks)++count[c]; priority_queue<pair<int, char>> pq; for (const auto& p : count)pq.push({ p.second,p.first }); int cycle = n + 1, time = 0; while (!pq.empty()) { vector<pair<int, char>> tmp; int tsk = 0; for (int i = 0; i < cycle; ++i) { if (!pq.empty()) { tmp.push_back(pq.top()); pq.pop(); ++tsk; } } for (auto& t : tmp) { if (–t.first) { pq.push(t); } } time += pq.empty() ? tsk : cycle; } return time; } }; [/cpp] 本代码提交AC,用时95MS。]]>

LeetCode Minimum Factorization

LeetCode Minimum Factorization Given a positive integer a, find the smallest positive integer b whose multiplication of each digit equals to a. If there is no answer or the answer is not fit in 32-bit signed integer, then return 0. Example 1 Input:

48
Output:
68
Example 2 Input:
15
Output:
35

给定一个数a,要求找一个最小的数b,使得b的各位数字相乘的结果等于a。 很有意思的一个题,考察数学,当时没做出来,参考了这个题。 首先,如果a小于10的话,那么最小的b就是a了,否则b就要变成十位数或者百位数。比如a=8,则最小的b就是8,当然b也可以是十位数比如24或者42,但是都大于个位数8自己。 当a大于等于10时,我们需要找a的因子,为了是b最小,则我们希望b的位数越少越好。所以我们要找b的因子应该越大越好。所以我们从最大的因子9开始找起,从9一直找到2,把a的所有因子都找出来。比如48从大到小的因子是8和6,则最终结果就是把因子从小到大组成一个数,即68。 这里有一个问题,48的因子还可以是4和2呀,为什么没有了呢。因为我们从9→2开始找因子的过程中,是用a除以因子得到的商来不断的找。上面48找到8和6的因子之后,商就等于1了,不需要再找因子了,所以结束。 如果在找因子的过程中,发现商变成了一个质数,因为大于10的质数不可能被分解,所以无解,返回0。 代码如下: [cpp] class Solution { public: int smallestFactorization(int a) { if (a < 10)return a; vector<int> factors; for (int i = 9; i >= 2; –i) { while (a%i == 0) { factors.push_back(i); a /= i; } } if (a != 1)return 0; // caution long long ans = 0; for (int i = factors.size() – 1; i >= 0; –i) { ans = ans * 10 + factors[i]; } return ans > INT_MAX ? 0 : ans; } }; [/cpp] 本代码提交AC,用时3MS。]]>

LeetCode Add One Row to Tree

LeetCode Add One Row to Tree Given the root of a binary tree, then value v and depth d, you need to add a row of nodes with value v at the given depth d. The root node is at depth 1. The adding rule is: given a positive integer depth d, for each NOT null tree nodes N in depth d-1, create two tree nodes with value v as N's left subtree root and right subtree root. And N's original left subtree should be the left subtree of the new left subtree root, its original right subtree should be the right subtree of the new right subtree root. If depth d is 1 that means there is no depth d-1 at all, then create a tree node with value v as the new root of the whole original tree, and the original tree is the new root’s left subtree. Example 1:

Input:
A binary tree as following:
       4
     /   \
    2     6
   / \   /
  3   1 5
v = 1
d = 2
Output:
       4
      / \
     1   1
    /     \
   2       6
  / \     /
 3   1   5
Example 2:
Input:
A binary tree as following:
      4
     /
    2
   / \
  3   1
v = 1
d = 3
Output:
      4
     /
    2
   / \
  1   1
 /     \
3       1
Note:
  1. The given d is in range [1, maximum depth of the given tree + 1].
  2. The given binary tree has at least one tree node.

在二叉树深度为d的那一层增加一行值全为v的节点,如果d=1的话,新增一个值为v的根节点,原来的树作为新根节点的左子树。 简单题,层次遍历到深度为d-1时,对d-1层的所有节点,都增加值为v的左右孩子,如果d-1层的节点原来有左右孩子,则原来的左右孩子接到新加入的v的节点上,作为左右孩子。 完整代码如下: [cpp] class Solution { public: TreeNode* addOneRow(TreeNode* root, int v, int d) { if (d == 1) { TreeNode* newRoot = new TreeNode(v); newRoot->left = root; return newRoot; } queue<TreeNode*> tree; tree.push(root); int depth = 0; while (!tree.empty()) { ++depth; int n = tree.size(); if (depth == d – 1) { for (int i = 0; i < n; ++i) { TreeNode* p = tree.front(); tree.pop(); if (p == NULL)continue; TreeNode* l = new TreeNode(v); l->left = p->left; p->left = l; TreeNode* r = new TreeNode(v); r->right = p->right; p->right = r; } break; } else { for (int i = 0; i < n; ++i) { TreeNode* p = tree.front(); tree.pop(); if (p == NULL)continue; tree.push(p->left); tree.push(p->right); } } } return root; } }; [/cpp] 本代码提交AC,用时16MS。]]>

LeetCode Maximum Distance in Arrays

LeetCode Maximum Distance in Arrays Given m arrays, and each array is sorted in ascending order. Now you can pick up two integers from two different arrays (each array picks one) and calculate the distance. We define the distance between two integers a and b to be their absolute difference |a-b|. Your task is to find the maximum distance. Example 1:

Input:
[[1,2,3],
 [4,5],
 [1,2,3]]
Output: 4
Explanation:
One way to reach the maximum distance 4 is to pick 1 in the first or third array and pick 5 in the second array.
Note:
  1. Each given array will have at least 1 number. There will be at least two non-empty arrays.
  2. The total number of the integers in all the m arrays will be in the range of [2, 10000].
  3. The integers in the m arrays will be in the range of [-10000, 10000].

给定m个已从小到大排序的数组,要求从两个不同的数组中取出两个数,使得这两个数的差的绝对值最大,问最大的差的绝对值是多少。 因为数组已排序,所以最大的差值肯定等于某个数组的最大值和另一个数组的最小值的差。这里唯一需要注意的是两个数必须选自两个不同的数组。 我开始的做法是这样的,把所有数组的最大值和最小值都拿出来,然后重新排个序。在新数组中,如果最大值和最小值来自不同的数组,则他们的差就是最终结果。加入最大值和最小值是来自同一个数组的,则求最大值和次小值的差以及次大值和最小值的差中的最大值。因为最大值和最小值来自同一个数组,所以最大值和次小值以及次大值和最小值肯定不是来自同一个数组的。 代码如下: [cpp] class Solution { struct P { int idx; int value; P(int i, int v) :idx(i), value(v) {}; bool operator<(const P& p) { return this->value < p.value; } }; public: int maxDistance(vector<vector<int>>& arrays) { vector<P> vp; for (int i = 0; i < arrays.size(); ++i) { vp.push_back({ i,arrays[i][0] }); vp.push_back({ i,arrays[i][arrays[i].size() – 1] }); } sort(vp.begin(), vp.end()); int ans = 0, n = vp.size(); if (vp[0].idx != vp[n – 1].idx)return abs(vp[n – 1].value – vp[0].value); return max(abs(vp[n – 1].value – vp[1].value), abs(vp[n – 2].value – vp[0].value)); } }; [/cpp] 本代码提交AC,用时26MS。因为要对m个数组的最大值和最小值排序,所以时间复杂度为O((2m)lg(2m))。 讨论区还有一种解法,只需要O(m)的复杂度。我们遍历每个数组,记录之前所有数组的最小值的最小值以及最大值的最大值,然后用当前数组的最大值和最小值减去之前所有数组中的最小值的最小值以及最大值的最大值,求绝对值,更新结果。这样就作差的两个数肯定来自不同的数组,避免了之前的问题。 代码如下: [cpp] class Solution { public: int maxDistance(vector<vector<int>>& arrays) { int ans = INT_MIN; int curMin = arrays[0][0], curMax = arrays[0][arrays[0].size() – 1]; for(int i = 1; i < arrays.size(); ++i) { ans = max(ans, abs(arrays[i][0] – curMax)); ans = max(ans, abs(arrays[i][arrays[i].size() – 1] – curMin)); curMin = min(curMin, arrays[i][0]); curMax = max(curMax, arrays[i][arrays[i].size() – 1]); } return ans; } }; [/cpp] 本代码提交AC,用时22MS。]]>

LeetCode LRU Cache

146. LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.

get(key) – Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) – Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

The cache is initialized with a positive capacity.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

实现一个LUR cache。 我们知道数据的访问都有时间局部性和空间局部性,所以为了加快访问速度,把经常访问的数据放到高速缓存cache中,这样先查cache,不命中再查内存或者外存。 但是cache一般都很小,当新数据需要插入cache时,如果cache满,则需要替换掉cache中旧的元素。此时替换哪个元素就有很多策略了,可以把最久没访问的替换出去,也可以把访问频率最低的替换出去,还可以把最先进入的替换出去等等,各种策略就形成了不同的缓存替换算法。 LRU的意思是Least Recently Used (LRU),即把最近最久没访问的元素替换出去,是指时间上最久没访问的,而不是频率上访问最少的哦。 题目要求实现LRU的两个接口,分别是

  1. get(key),从LRU中获取key对应的value,如果LRU中不存在key,则返回-1
  2. put(key,value),把key和对应的value插入到LRU中,如果LRU满,则要替换掉最近最久没访问的元素

要求这两个操作都在O(1)时间内完成。 我们来分析一下应该用哪些数据结构使这两个操作都在O(1)时间内完成。首先get需要在O(1)时间内判断key是否在LRU中,所以可能需要用hash来存储<key,value>对。其次,需要缓存替换时,要在O(1)时间内把最久没访问的<key,value>删掉,同时插入新的<key,value>,删除和插入能在O(1)时间内完成的就是链表了。

综合上面的分析,我们可以用unordered_map+list来实现LRU,STL的list是一个双向链表。 参考这份代码。我们使用list<int> used来保存当前在LRU中的那些key,注意是key;用unordered_map<int, pair<int, list<int>::iterator>> cache来保存真实的缓存,cache的key就是LRU中现存的key,value是一个pair,保存了key对应的value,以及这个key在链表中的迭代器(指针)。

两个操作的实现如下:

put(key,value)。每当有新<key,value>进入时,如果key已经在cache中,则只需要更新key对应的value,同时,刚访问的元素下次很可能再次访问,所以需要把这个key提到链表的表头(为了不让这个key沉到表尾)。如果key不在cache中,但cache已满,则需要把链表尾部的key对应的数据在cache中删掉,同时也需要在链表中删掉这个尾节点;如果cache不满,则把新key插入到链表表头,同时把<key,value>插入到cache中,且key在链表中的位置是list.begin()。

get(key)。如果key不在cache中,直接返回-1。如果key在cache中,直接从cache中得到对应的value,同时根据迭代器,把key提到链表表头。 根据上述算法描述,我们需要抽象一个函数,moveToHead(list<int>::iterator &it),即把迭代器it指向的那个key移到表头,包括两个操作,把it从it位置删掉,把it的值插到表头。

完整代码如下:

class LRUCache {
private:
    unordered_map<int, pair<int, list<int>::iterator> > cache;
    list<int> used;
    int _capacity; // 把it节点**剪切**到表头
    void moveToHead(list<int>::iterator& it)
    {
        int key = *it;
        used.erase(it);
        used.push_front(key);
        cache[key].second = used.begin();
    }
public:
    LRUCache(int capacity)
        : _capacity(capacity)
    {
    }
    int get(int key)
    {
        if (cache.find(key) == cache.end())
            return -1;
        int value = cache[key].first;
        moveToHead(cache[key].second);
        return value;
    }
    void put(int key, int value)
    {
        if (cache.find(key) != cache.end()) {
            cache[key].first = value;
            moveToHead(cache[key].second);
        }
        else {
            if (used.size() == _capacity) {
                cache.erase(used.back());
                used.pop_back();
            }
            used.push_front(key);
            cache[key] = { value, used.begin() };
        }
    }
};

本代码提交AC,用时96MS。

我们还可以自己实现list,就是一个双向链表。这个双向链表包括头结点head和尾节点tail。为了插入删除的方便,我们设置了一个用不到的head和tail,所以真正的数据其实是在head和tail之间的,如下,节点1~3才是真正的数据节点

头 --> 节 --> 节 --> 节 --> 尾
节     点     点     点     节
点 <-- 1  <-- 2 <-- 3  <-- 点

这里我们抽象出了3个函数,removeFromList和moveToHead完成上一种实现中的moveToHead功能,即先把节点从原链表中删除(相当于摘下来),然后把这个节点安放到表头(上图节点1)。removeTail就是在需要替换时,删除表尾节点(上图节点3)。

完整代码如下:

class LRUCache {
private:
    struct Node {
        int key, value;
        Node *pre, *next;
        Node(int k, int v)
            : key(k)
            , value(v)
            , pre(NULL)
            , next(NULL){};
    };
    unordered_map<int, Node*> cache;
    Node *head, *tail;
    int _capacity; // 从原链表中删除节点node(摘下)
    void removeFromList(Node* node)
    {
        node->pre->next = node->next;
        node->next->pre = node->pre;
    }
    // 把节点node放到“表头”(安放)
    void moveToHead(Node* node)
    {
        node->next = head->next;
        head->next->pre = node;
        node->pre = head;
        head->next = node;
    }
    // 删除“尾节点”
    void removeTail()
    {
        Node* target = tail->pre;
        target->pre->next = target->next;
        target->next->pre = target->pre;
        delete target;
    }

public:
    LRUCache(int capacity)
        : _capacity(capacity)
    {
        head = new Node(0, 0); // 辅助头结点
        tail = new Node(0, 0); // 辅助尾节点
        head->next = tail;
        tail->pre = head;
    }
    int get(int key)
    {
        if (cache.find(key) == cache.end())
            return -1;
        int value = cache[key]->value;
        removeFromList(cache[key]);
        moveToHead(cache[key]);
        return value;
    }
    void put(int key, int value)
    {
        if (cache.find(key) != cache.end()) {
            cache[key]->value = value;
            removeFromList(cache[key]);
            moveToHead(cache[key]);
        }
        else {
            if (cache.size() == _capacity) {
                cache.erase(tail->pre->key);
                removeTail();
            }
            Node* node = new Node(key, value);
            cache[key] = node;
            moveToHead(node);
        }
    }
};

本代码提交AC,用时98MS。

LeetCode Lowest Common Ancestor of a Binary Tree

LeetCode Lowest Common Ancestor of a Binary Tree Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4
For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
给定一棵二叉树和两个节点p,q,问p和q的最近公共祖先节点是哪个。LCA问题。 思路:要找p和q的最近公共祖先,很直观的方法是找到从根节点分别到p和q的路径,然后把其中一条路径(比如根到p)上的点hash成path1,从另一条路径(比如根到q)的底端(q)往根节点走,把走过的每个点去path1中找,如果找到,则就是他们的LCA,因为这是距离q最近、且在path1中的点。 但是怎样找到根到p和q的路径呢。这里换一种思路,定义如下新的数据结构,par表示该节点的父节点,根节点的父节点为-1。 [cpp] struct MyNode { TreeNode* node; int par; MyNode(TreeNode* n_, int p_) :node(n_), par(p_) {}; }; [/cpp] 先整体把树层次遍历一遍,成为一个保存MyNode的数组,记录每个点的父节点在数组中的下标。在遍历的同时,记录下p和q节点的下标。这样通过下标往前递归就可以找到p和q到根节点的路径了。 找到路径之后,对其中一条路径hash,另一条路径在该hash中找。代码如下: [cpp] class Solution { private: struct MyNode { TreeNode* node; int par; MyNode(TreeNode* n_, int p_) :node(n_), par(p_) {}; }; public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { vector<MyNode> nodes; int i = 0, j = 0; queue<MyNode> tree; tree.push({ root,-1 }); while (!tree.empty()) { MyNode cur = tree.front(); tree.pop(); if (cur.node == NULL) continue; if (cur.node == p)i = nodes.size(); if (cur.node == q)j = nodes.size(); tree.push({ cur.node->left,nodes.size() }); tree.push({ cur.node->right,nodes.size() }); nodes.push_back(cur); } if (i == j)return nodes[i].node; unordered_set<int> path1; while (i != -1) { path1.insert(i); i = nodes[i].par; } while (j != -1) { if (path1.find(j) != path1.end()) { return nodes[j].node; } j = nodes[j].par; } return NULL; } }; [/cpp] 本代码提交AC,用时19MS。 讨论区还有一种递归的解法,很有意思。我们在以root为根的树中找p和q的LCA,如果root等于p或q其中之一,说明p或q就是他们的LCA。否则分别在左子树和右子树中递归的找LCA,如果发现在左右子树中找到的LCA都不为null,说明他们p和q分别位于root的两个子树,类似样例中的5和1,则root就是他们的LCA。如果某个子树返回的LCA为空,说明p和q都不在这个子树中,则先遇到谁,谁就是LCA,比如样例中的5和4,都不在3的右子树,在递归3的左子树的时候,先遇到了5,所以5是LCA。 完整代码如下: [cpp] class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (root == NULL || root == p || root == q)return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); if (left != NULL&&right != NULL)return root; return left != NULL ? left : right; } }; [/cpp] 本代码提交AC,用时23MS。]]>

LeetCode Integer Replacement

LeetCode Integer Replacement Given a positive integer n and you can do operations as follow:

  1. If n is even, replace n with n/2.
  2. If n is odd, you can replace n with either n + 1 or n - 1.
What is the minimum number of replacements needed for n to become 1? Example 1:
Input:
8
Output:
3
Explanation:
8 -> 4 -> 2 -> 1
Example 2:
Input:
7
Output:
4
Explanation:
7 -> 8 -> 4 -> 2 -> 1
or
7 -> 6 -> 3 -> 2 -> 1

给定一个数n,不断对n进行如下两种操作中的一种
  1. 如果n是偶数,把n变成n/2
  2. 如果n是奇数,把n变成n+1或者n-1
问要把n变为1,需要的最少的变换次数是多少。 为啥我首先想到的是BFS。。。这不就相当于每个点根据情况可以走不同的方向,问走到终点1需要的最少步数吗?很像BFS呀。代码如下: [cpp] class Solution { private: struct P { long long val; int step; P(long long v_, int s_) :val(v_), step(s_) {}; }; public: int integerReplacement(int n) { P p(n, 0); queue<P> qp; qp.push(p); while (!qp.empty()) { P f = qp.front(); qp.pop(); if (f.val == 1)return f.step; else if (f.val & 1) { qp.push(P(f.val + 1, f.step + 1)); qp.push(P(f.val – 1, f.step + 1)); } else { qp.push(P(f.val / 2, f.step + 1)); } } return 0; } }; [/cpp] 本代码提交AC,用时6MS,只击败5%的人。。。 看讨论区,用位运算求解。要想快速的到达1,则n的二进制中0越多越好,因为0越多,后续越有可能用n/2快速的去掉一位。所以如果n是奇数时,我们判断一下n+1和n-1哪个包含的0越多,就走哪步。 但是每次都需要求n+1和n-1中1的个数,复杂度有点高。 其实,我们只需要关注n的最后两个二进制位即可。任何一个数的二进制尾数最后两位可能有四种情况,00、01、10、11,如果n是偶数,即以00或10结尾,则显然n/2能快速的减少一位。但是如果是01或者11呢。如果是01,则减1会变成00,如果加1,会变成10。显然,减1之后多出一个0,而加1之后0的个数没变,所以如果是以01结尾,则减1更优。 如果末尾是11,则减1变成10,加1变成100,显然加1更优。 有一个例外是,如果n就等于3,即二进制为11,则11→10→1比11→100→10→1更优,即当n==3时,减1更好。 完整代码如下: [cpp] class Solution { public: int integerReplacement(int n) { if (n == INT_MAX)return 32; int ans = 0; while (n > 1) { if ((n & 1) == 0) n >>= 1; else if (n == 3 || ((n >> 1) & 1) == 0)–n; else ++n; ++ans; } return ans; } }; [/cpp] 本代码提交AC,用时3MS。]]>

LeetCode Longest Uncommon Subsequence II

LeetCode Longest Uncommon Subsequence II Given a list of strings, you need to find the longest uncommon subsequence among them. The longest uncommon subsequence is defined as the longest subsequence of one of these strings and this subsequence should not be any subsequence of the other strings. A subsequence is a sequence that can be derived from one sequence by deleting some characters without changing the order of the remaining elements. Trivially, any string is a subsequence of itself and an empty string is a subsequence of any string. The input will be a list of strings, and the output needs to be the length of the longest uncommon subsequence. If the longest uncommon subsequence doesn’t exist, return -1. Example 1:

Input: "aba", "cdc", "eae"
Output: 3
Note:
  1. All the given strings’ lengths will not exceed 10.
  2. The length of the given list will be in the range of [2, 50].

给定一个字符串数组,问这些字符串的最长非公共子序列的长度是多少。非公共子序列是指任意一个字符串的子序列,不是其他所有字符串的子序列。要求这样的子序列的最大长度。 首先非公共子序列不可能来自那些出现过多次的字符串,比如数组中有两个字符串都是”abcd”,那么其中一个”abcd”的任意一个子序列,必定是另一个”abcd”的子序列,不满足非公共子序列的定义。所以我们首先对所有字符串及其出现频率hash一下,只考察那些出现一次的字符串的子序列。 另一个观察是,如果存在非公共子序列,则最长的非公共子序列肯定是某个完整的字符串,这一点从LeetCode Longest Uncommon Subsequence I可知。 所以,我们首先把字符串按长度从大到小排序,排序完之后,遍历每个字符串s,如果s出现的频率大于1,直接不考虑;否则,采用贪心策略,我们在比s更长的那些字符串中判断s是否是他们的子序列,如果是,s不是非公共子序列;否则s是非公共子序列,因为后面都是比s更短的字符串,s肯定也不是他们的子序列,又我们根据字符串长度排序了。所以s的长度就是非公共子序列的最大长度。 完整代码如下: [cpp] class Solution { private: // 判断child是否是par的子序列 bool isSubSeq(const string& par, const string& child) { int i = 0, j = 0, m = par.size(), n = child.size(); while (i < m&&j < n) { if (par[i] == child[j])++j; ++i; } return j == n; } public: int findLUSlength(vector<string>& strs) { unordered_map<string, int> hash; for (const auto& s : strs)++hash[s]; sort(strs.begin(), strs.end(), [](const string& s1, const string& s2) {return s1.size() > s2.size(); }); for (int i = 0; i < strs.size(); ++i) { if (hash[strs[i]] > 1)continue; bool isSub = false; for (int j = i – 1; j >= 0; –j) { if (isSubSeq(strs[j], strs[i])) { isSub = true; break; } } if (!isSub)return strs[i].size(); } return -1; } }; [/cpp] 本代码提交AC,用时3MS。 参考:http://bookshadow.com/weblog/2017/04/03/leetcode-longest-uncommon-subsequence-ii/]]>