Showing posts with label queue. Show all posts
Showing posts with label queue. Show all posts

Thursday, November 27, 2014

[LeetCode] Word Ladder I, II

Word Ladder I

Given two words (start and end), and a dictionary, find the length of shortest transformation sequence from start to end, such that:
  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
Note:
  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.


Word Ladder II

Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
  [
    ["hit","hot","dot","dog","cog"],
    ["hit","hot","lot","log","cog"]
  ]
Note:
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.


思路:

LeetCode中为数不多的考图的难题。尽管题目看上去像字符串匹配题,但从“shortest transformation sequence from start to end”还是能透露出一点图论中最短路径题的味道。如何转化?

1. 将每个单词看成图的一个节点。
2. 当单词s1改变一个字符可以变成存在于字典的单词s2时,则s1与s2之间有连接。
3. 给定s1和s2,问题I转化成了求在图中从s1->s2的最短路径长度。而问题II转化为了求所有s1->s2的最短路径。

无论是求最短路径长度还是求所有最短路径,都是用BFS。在BFS中有三个关键步骤需要实现:

1. 如何找到与当前节点相邻的所有节点。
这里可以有两个策略:
(1) 遍历整个字典,将其中每个单词与当前单词比较,判断是否只差一个字符。复杂度为:n*w,n为字典中的单词数量,w为单词长度。
(2) 遍历当前单词的每个字符x,将其改变成a~z中除x外的任意一个,形成一个新的单词,在字典中判断是否存在。复杂度为:26*w,w为单词长度。
这里可以和面试官讨论两种策略的取舍。对于通常的英语单词来说,长度大多小于100,而字典中的单词数则往往是成千上万,所以策略2相对较优。

2. 如何标记一个节点已经被访问过,以避免重复访问。
可以将访问过的单词从字典中删除。

3. 一旦BFS找到目标单词,如何backtracking找回路径?



Word Ladder I

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
    int ladderLength(string start, string end, unordered_set<string> &dict) {
        dict.insert(end);
        queue<pair<string,int>> q;
        q.push(make_pair(start,1));
        while(!q.empty()) {
            string s = q.front().first;
            int len = q.front().second;
            if(s==end) return len;
            q.pop();
            vector<string> neighbors = findNeighbors(s, dict);
            for(int i=0; i<neighbors.size(); i++) 
                q.push(make_pair(neighbors[i],len+1));
        }
        return 0;
    }
    
    vector<string> findNeighbors(string s, unordered_set<string> &dict) {
        vector<string> ret;
        for(int i=0; i<s.size(); i++) {
            char c = s[i];
            for(int j=0; j<26; j++) {
                if(c=='a'+j) continue;
                s[i] = 'a'+j;
                if(dict.count(s)) {
                    ret.push_back(s);    
                    dict.erase(s);    
                }
            }
            s[i] = c;
        }
        return ret;
    }
};


Word Ladder II


Tuesday, November 11, 2014

[LeetCode] Binary Tree Level Order Traversal I, II

Binary Tree Level Order Traversal


Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).
For example:
Given binary tree {3,9,20,#,#,15,7},
    3
   / \
  9  20
    /  \
   15   7
return its level order traversal as:
[
  [3],
  [9,20],
  [15,7]
]

Binary Tree Level Order Traversal II
Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left to right, level by level from leaf to root).
For example:
Given binary tree {3,9,20,#,#,15,7},
    3
   / \
  9  20
    /  \
   15   7
return its bottom-up level order traversal as:
[
  [15,7],
  [9,20],
  [3]
]

思路:

这两题实际是一回事,考察的都是树的层序访问。无非就是在I得到结果后做一次reverse就能得到II要求的结果。无论是树还是图,层序访问最直接的方法就是BFS。

1. BFS可以用一个queue实现,但是难以跟踪每个节点究竟是在第几层。解决办法是除了压入节点指针外,还同时压入节点所在的层数,即压入pair<TreeNode*, int>。

2. 层序访问更直接的方法是将当前层的节点和下一层的节点分别保存在两个container curLevel和nextLevel中(vector, queue, stack都可以,取决于具体要求)。每次扫描curLevel中的节点,并将其左右子节点更新到nextLevel中。当curLevel所有节点扫描过后,该层已经遍历完整,swap curLevel和nextLevel即可继续访问。


1. 两个container的迭代解法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
    vector<vector<int> > levelOrder(TreeNode *root) {
        vector<vector<int>> levelNodeValues;
        if(!root) return levelNodeValues;
        
        vector<TreeNode*> *curLevel = new vector<TreeNode*>();
        vector<TreeNode*> *nextLevel = new vector<TreeNode*>();
        curLevel->push_back(root);
        
        while(!curLevel->empty()) {
            // scan curLevel, collect values of curLevel nodes, and generate nextLevel
            vector<int> curLevelValues;
            for(int i=0; i<curLevel->size(); i++) {
                TreeNode *curNode = (*curLevel)[i];
                curLevelValues.push_back(curNode->val);
                if(curNode->left) nextLevel->push_back(curNode->left);
                if(curNode->right) nextLevel->push_back(curNode->right);
            }
            levelNodeValues.push_back(curLevelValues);
            
            //swap curLevel and nextLevel, and clear nextLevel
            vector<TreeNode*> *temp = curLevel;
            curLevel = nextLevel;
            nextLevel = temp;
            nextLevel->clear();
        }
        return levelNodeValues;        
    }
};


2. 单个queue的迭代解法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
    struct levelNode {
        TreeNode *node;
        int level;
        levelNode(TreeNode *nd, int lvl):node(nd), level(lvl){}
    };
    
public:
    vector<vector<int> > levelOrder(TreeNode *root) {
        vector<vector<int>> levelNodeValues;
        if(!root) return levelNodeValues;
        
        queue<levelNode> q;
        q.push(levelNode(root,0));
        
        while(!q.empty()) {
            levelNode curLevelNode = q.front();
            TreeNode *curNode = curLevelNode.node;
            int curLevel = curLevelNode.level;
            q.pop();
            
            if(curLevel==levelNodeValues.size())
                levelNodeValues.push_back(vector<int>(0,0));
            levelNodeValues[curLevel].push_back(curNode->val);
            
            if(curNode->left) q.push(levelNode(curNode->left,curLevel+1));
            if(curNode->right) q.push(levelNode(curNode->right,curLevel+1));
        }
        
        return levelNodeValues;        
    }
};

注意ln 22-23需要检查当前节点是否在新的一层,如果是新的一层第一个节点,则要相应增加levelNodeValues的尺寸。


3. 层尾标记法

同样用一个queue来进行BFS,但queue的元素仅为TreeNode*而不需要存储节点的层数。在每一层访问完毕时push一个NULL进queue作为一层结束的标记。这种方法的对于平衡binary tree来说增加了log(n)次的push(NULL)和pop()。但如果是非平衡二叉树的极端情况,可能会增加n次的push和pop。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public:
    vector<vector<int> > levelOrder(TreeNode *root) {
        vector<vector<int>> levelNodeValues;
        if(!root) return levelNodeValues;
        
        queue<TreeNode*> q;
        q.push(root);
        q.push(NULL);
        int curLevel = 0;
        
        while(!q.empty()) {
            TreeNode *cur = q.front();
            q.pop();
            
            if(!cur) {
                curLevel++;
                if(!q.empty()) q.push(NULL);
                continue;
            }
            
            if(curLevel==levelNodeValues.size())
                levelNodeValues.push_back(vector<int>(0,0));
            levelNodeValues[curLevel].push_back(cur->val);
            
            if(cur->left) q.push(cur->left);
            if(cur->right) q.push(cur->right);
        }
        
        return levelNodeValues;        
    }
};

注意:
ln 9:在压入root后,需要额外压入一个NULL来标记第0层尾。
ln 18:只有在q不为空时才压入层尾标记NULL。少了这个判断,则在处理最后一个NULL时会陷入死循环——不断取出一个NULL,再压回一个NULL。
ln 22-23:和方法2中一样,对新的一层要扩展levelNodeValues的尺寸。


对于Binary Tree Level Order Traversal II,只需要再I的解的基础上,最后再翻转levelNodeValues即可:


1
reverse(levelNodeValues.begin(),levelNodeValues.end());

[LeetCode] Symmetric Tree

Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).
For example, this binary tree is symmetric:
    1
   / \
  2   2
 / \ / \
3  4 4  3
But the following is not:
    1
   / \
  2   2
   \   \
   3    3
Note:
Bonus points if you could solve it both recursively and iteratively.

1. 递归解法:

这题看着很tricky,实际上本质思路算法和Same Tree那题换汤不换药。让我们来比较下两题。

T(p)和T(q)是same tree的条件:

(1) p和q等价(同时为NULL,或同时不为NULL且val相同)

(2) T(p->left)和T(q->left)为same tree,且T(p->right)和T(q->right)为same tree。

T(r)是symmetric的条件:实际就是判断T(r->left)和T(r-right)是否互为镜像的问题。假设p和q分别指向同一个tree内部两个镜像对称的节点,例如题目例子中的两个节点2

(1) p和q等价(同时为NULL,或同时不为NULL且val相同)

(2) T(p->left)和T(q->right)必须互为镜像,且T(p->right)和T(q->left)必须互为镜像。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Solution {
public:
    bool isSymmetric(TreeNode *root) {
        return isSym(root, root);
    }
    
    bool isSym(TreeNode *lr, TreeNode *rr) {
        if(!lr && !rr) return true;
        if((!lr && rr) || (lr && !rr)) return false;
        if(lr==rr) return isSym(lr->left, lr->right); // lr and rr both point to root
        if(lr->val != rr->val) return false;
        return isSym(lr->left, rr->right) && isSym(lr->right, rr->left);
    }
};

总结:

ln 10有一个特殊处理,当lr和rr指向同一节点时,必然这个节点是整个树的root。此时避免重复判断,只要判断它的左和右子树是否互为镜像即可。



2. 迭代解法:

迭代解法本质上和递归解法是一回事,尽管理解上没有递归这么直观简洁。通常用stack或queue来暂时存放之后要处理的节点。这里由于要判断左右子树是否镜像,用两个queue分别对左右子树进行BFS的一层一层地访问。在访问中压入子节点到两个queue时,采用完全相反的顺序:对左子树先压入left child再right child;对右子树先压入right child再left child。这样一来将symmetric tree问题又转换为了same tree问题。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
    bool isSymmetric(TreeNode *root) {
        if(!root) return true;
        queue<TreeNode*> ql, qr;
        ql.push(root->left);
        qr.push(root->right);
        
        while(!ql.empty() && !qr.empty()) {
            TreeNode *lnode = ql.front();
            TreeNode *rnode = qr.front();
            ql.pop();
            qr.pop();
            
            if(!lnode && !rnode) continue;
            if((!lnode && rnode) || (lnode && !rnode)) return false;
            if(lnode->val != rnode->val) return false;
            ql.push(lnode->left);
            qr.push(rnode->right);
            ql.push(lnode->right);
            qr.push(rnode->left);
        }
        
        if(!ql.empty() || !qr.empty()) return false;
        return true;
    }
};


总结:

ln 15 -17对应了递归解法的ln 8, 9 , 11。除了用queue外,也可用两个stack进行左右镜像的dfs访问。