Monthly Archives: February 2017

LeetCode Validate Binary Search Tree

98. Validate Binary Search Tree

Given a binary tree, determine if it is a valid binary search tree (BST).

Assume a BST is defined as follows:

  • The left subtree of a node contains only nodes with keys less than the node’s key.
  • The right subtree of a node contains only nodes with keys greater than the node’s key.
  • Both the left and right subtrees must also be binary search trees.

Example 1:

    2
   / \
  1   3

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

Example 2:

    5
   / \
  1   4
     / \
    3   6

Input: [5,1,4,null,null,3,6]
Output: false
Explanation: The root node's value is 5 but its right child's value is 4.

验证一棵二叉树是否是二叉搜索树。二叉搜索树树需要满足左孩子小于等于根节点,根节点小于等于右孩子,本题中是严格的小于关系,所以二叉搜索树的中序遍历是严格递增的。一种解法是对二叉树中序遍历,然后检查遍历结果是否是严格递增的,这种解法很简单,不赘述了。
另外一种解法是直接用递归判断,代码如下。我们在每一次递归时,都判断当前节点是否大于最小值,且小于最大值,同时把当前节点的值递归带入。代码中没有求min和max的过程,但是每次调用的时候能正确检查,里面的奥秘需要自己体会,不太好讲。
举个例子吧,下图中节点6违反了BST的规则,虽然13所在子树是BST,但是6小于根节点10,所以不是BST。本代码是怎样判断出来的呢,首先根节点传入右孩子的约束是[10,max],15传入左孩子的约束是[10,15],13传入左孩子的约束是[10,13],所以6并不属于[10,13],不属于BST。

    10
   / \
  5   15
     /  \
    13   20
   /  \
  6   14
class Solution {
public:
    bool helper(TreeNode* root, long long minVal, long long maxVal)
    {
        if (root == NULL)
            return true;
        if (root->val <= minVal || root->val >= maxVal)
            return false;
        return helper(root->left, minVal, root->val) && helper(root->right, root->val, maxVal);
    }
    bool isValidBST(TreeNode* root)
    {
        long long minVal = LLONG_MIN, maxVal = LLONG_MAX;
        return helper(root, minVal, maxVal);
    }
};

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

二刷。原理相同,更好理解的代码:

class Solution {
public:
	bool isBST(TreeNode* root, int &max_val, int &min_val) {
		if (root == NULL) {
			max_val = INT_MIN;
			min_val = INT_MAX;
			return true;
		}
		// 以root为根的子树的最大和最小值
		max_val = root->val;
		min_val = root->val;
		if (root->left == NULL && root->right == NULL) {
			return true;
		}
		if (root->left) {
			int left_max = INT_MIN, left_min = INT_MAX;
			bool left_tree = isBST(root->left, left_max, left_min);
			if (!left_tree || left_max >= root->val)return false;
			min_val = min(min_val, left_min);
		}
		
		if (root->right) {
			int right_max = INT_MIN, right_min = INT_MAX;
			bool right_tree = isBST(root->right, right_max, right_min);
			if (!right_tree || right_min <= root->val)return false;
			max_val = max(max_val, right_max);
		}
		return true;
	}

	bool isValidBST(TreeNode* root) {
		int max_val = INT_MIN, min_val = INT_MAX;
		return isBST(root, max_val, min_val);
	}
};

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

LeetCode Find Mode in Binary Search Tree

LeetCode Find Mode in Binary Search Tree Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred element) in the given BST. Assume a BST is defined as follows:

  • The left subtree of a node contains only nodes with keys less than or equal to the node’s key.
  • The right subtree of a node contains only nodes with keys greater than or equal to the node’s key.
  • Both the left and right subtrees must also be binary search trees.
For example: Given BST [1,null,2,2],
   1
    \
     2
    /
   2
return [2]. Note: If a tree has more than one mode, you can return them in any order. Follow up: Could you do that without using any extra space? (Assume that the implicit stack space incurred due to recursion does not count).
找到二叉搜索树中所有的众数,如果有多个众数,都要给出。并且Follow up限制空间复杂度为O(1)。 很有意思的一个题,如果把二叉搜索树当成一棵普通的树,并且没有O(1)空间复杂度的限制,则在遍历时,可以使用hash统计每个数出现的频率,然后取最高频率的数。代码如下: [cpp] class Solution { public: void work(unordered_map<int, int>& hash, TreeNode* root, int& modCnt) { if (root == NULL)return; ++hash[root->val]; modCnt = max(modCnt, hash[root->val]); work(hash, root->left, modCnt); work(hash, root->right, modCnt); } vector<int> findMode(TreeNode* root) { unordered_map<int, int> hash; int modCnt = INT_MIN; work(hash, root, modCnt); vector<int> ans; unordered_map<int, int>::iterator it = hash.begin(); while (it != hash.end()) { if (it->second == modCnt)ans.push_back(it->first); ++it; } return ans; } }; [/cpp] 本代码提交AC,用时25MS。 如果使用二叉搜索树的特点并且考虑O(1)的空间复杂度,就有点复杂了。因为二叉搜索树的左孩子小于等于根节点,根节点小于等于右孩子,所以如果对二叉搜索树中序遍历的话,得到的序列就是一个非递减序列,也就是说是一个有序序列,那就好办多了。比如给定序列[1,2,2,3,4,4,5,6],要找出数组中的众数,并且限制O(1)空间复杂度,应该就不难了。 因为要O(1)空间复杂度,就不能用hash统计频率了,因为众数可能有多个,我们没法在一次遍历时就既找到最大的频率,又找到众数,所以可以采取两次遍历的策略。第一遍先找到最大的频率,上面的例子中是2(2和4都出现了两次);第二遍再把频率等于2的数值找出来,就是众数了。 对应到二叉搜索树中,就是对树中序遍历两次,第一次得到最大的频率以及众数的个数,第二遍再把所有众数都找出来。代码如下: [cpp] class Solution { private: vector<int> mods; int curVal = 0, curCnt = 0, modCnt = 0, maxCnt = 0; void handleValue(int v) { if (v != curVal) { curVal = v; curCnt = 0; } ++curCnt; if (curCnt > maxCnt) { // 第二次遍历时不会走此分支 maxCnt = curCnt; modCnt = 1; } else if (curCnt == maxCnt) { if (!mods.empty())mods[modCnt] = curVal; ++modCnt; } } void inorder(TreeNode* root) { if (root == NULL)return; inorder(root->left); handleValue(root->val); inorder(root->right); } public: vector<int> findMode(TreeNode* root) { inorder(root); mods.resize(modCnt); curCnt = 0; modCnt = 0; inorder(root); return mods; } }; [/cpp] 本代码提交AC,用时19MS。 参考:https://discuss.leetcode.com/topic/77335/proper-o-1-space]]>

LeetCode Serialize and Deserialize BST

LeetCode Serialize and Deserialize BST Serialization is the process of 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 just 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. Note: Do not use class member/global/static variables to store states. Your serialize and deserialize algorithms should be stateless.


本题要序列化和反序列化二叉搜索树。因为二叉搜索树属于二叉树,所以可以直接用LeetCode Serialize and Deserialize Binary Tree对普通二叉树的序列化和反序列化方法。但是二叉搜索树又有其特殊性,即左孩子小于等于根节点,根节点小于等于右孩子。所以如果知道树中有哪些数,通过插入的方法就可以构造好二叉搜索树。 首先还是根据层次遍历或者树的先序遍历得到树的序列化表示,那么序列化的第一个数就是根节点了。反序列化时,依次插入后续的节点构成一棵二叉搜索树就好了。但是数值插入的顺序不能乱,比如下图,虽然确定了根节点是1,如果先插入了4号节点,再插入3号节点,则反序列化得到的树就不是原来的树了。为了保证序列化到的数值顺序正确,可以使用层次遍历或者树的先序遍历。 完整代码如下: [cpp] class Codec { public: // Encodes a tree to a single string. string serialize(TreeNode* root) { if (root == NULL)return ""; string ans = to_string(root->val); if (root->left) ans += ","+serialize(root->left); if (root->right) ans += ","+serialize(root->right); return ans; } void insert(TreeNode* root, TreeNode* node) { if (node->val < root->val) { if (root->left == NULL)root->left = node; else insert(root->left, node); } else { if (root->right == NULL)root->right = node; else insert(root->right, node); } } // Decodes your encoded data to tree. TreeNode* deserialize(string data) { if (data == "")return NULL; TreeNode* root = NULL; for (int start = 0, end = 0; end <= data.size(); ++end) { if (data[end] == ‘,’ || end == data.size()) { TreeNode* node = new TreeNode(atoi(data.substr(start, end – start).c_str())); if (root == NULL)root = node; else insert(root, node); start = end + 1; } } return root; } }; [/cpp] 本代码提交AC,用时26MS。]]>

LeetCode Serialize and Deserialize Binary Tree

297. Serialize and Deserialize Binary Tree

Serialization is the process of 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 tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

Example: 

You may serialize the following tree:

    1
   / \
  2   3
     / \
    4   5

as "[1,2,3,null,null,4,5]"

Clarification: The above format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.

Note: Do not use class member/global/static variables to store states. Your serialize and deserialize algorithms should be stateless.


序列化一棵二叉树。要求把一棵二叉树转换为一个字符串,并且能根据这个字符串反序列化回原来的二叉树。
首先我们使用LeetCode Binary Tree Level Order Traversal层次遍历的方法得到二叉树的层次遍历序列,然后把数字转换为字符串并用逗号拼接起所有字符串,遇到空节点,则用空字符串表示。比如下面这棵树,序列化成字符串为:1,2,3,,,4,,,5。本来5后面还有空串的,即3的右孩子及其孩子,但是为了节省空间,后面的空串和逗号都省略了。

反序列化有点技巧。正常如果是一棵完全二叉树,如果下标从0开始,则节点i的左右孩子的下标分别为2*i+1和2*i+2,所以我们可以用递归的方法快速反序列化出来,就像我在LeetCode Minimum Depth of Binary Tree给出的根据数组构建树结构的genTree函数一样。对应到上图,完全二叉树的数组表示应该是{1,2,3,-1,-1,4,-1,-1,-1,-1,-1,-1,5,-1,-1},只有把这个数组传入genTree函数才能生成像上图的二叉树。
因为经过层次遍历之后的数组是{1,2,3,-1,-1,4,-1,-1,5},也就是说2后面只保留了其直接的两个空节点,并没有保留其4个空的孙子节点。导致4号节点(下标为5)在找孩子节点时,使用2*i+1=11和2*i+2=12找左右孩子的下标时,数组越界,导致漏掉了右孩子5。
后来参考了LeetCode官方的二叉树反序列化作者的介绍,恍然大悟。因为数组是层次遍历的结果,所以兄弟节点的孩子节点是按先后顺序存储在数组中的,我们可以分别维护一个父亲节点par和孩子节点child,一开始par=0,child=1,表示数组的0号节点为父亲节点,1号节点为左孩子节点,然后我们把child接到par的左孩子,把child+1接到par的右孩子,par++,child+=2,当遇到child为空,也要把空节点接到par上面。当遇到par为空时,直接跳过就好,但是child不移动。这样就能避免父亲为空,孩子节点的下标对不上的问题。比如上例中,par指向数值3时,左child指向数值4,右child指向-1;当par指向后面的两个-1时,child不动;当par指向4时,左child指向下一个-1,右child指向5正确。
完整代码如下:

class Codec {
public:
    string bfs(TreeNode* root)
    {
        if (root == NULL)
            return "";
        string tree = "";
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            TreeNode* t = q.front();
            q.pop();
            if (t == NULL) {
                tree += ",";
                continue;
            }
            tree += to_string(t->val) + ",";
            q.push(t->left);
            q.push(t->right);
        }
        return tree;
    }
    // Encodes a tree to a single string.
    string serialize(TreeNode* root)
    {
        string tree = bfs(root);
        if (tree == "")
            return "";
        int end = tree.size() – 1;
        while (tree[end] == ‘,’)
            –end;
        return tree.substr(0, end + 1);
    }
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data)
    {
        if (data == "")
            return NULL;
        vector<TreeNode*> tree;
        for (int start = 0, end = 0; end <= data.size(); ++end) {
            if (data[end] == ‘,’ || end == data.size()) {
                string tmp = data.substr(start, end – start);
                if (tmp == "")
                    tree.push_back(NULL);
                else
                    tree.push_back(new TreeNode(atoi(tmp.c_str())));
                start = end + 1;
            }
        }
        int par = 0, child = 1;
        while (child < tree.size()) {
            if (tree[par]) {
                if (child < tree.size())
                    tree[par]->left = tree[child++];
                if (child < tree.size())
                    tree[par]->right = tree[child++];
            }
            ++par;
        }
        return tree[0];
    }
};

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

二刷。逻辑性更好的代码:

class Codec {
public:

	// Encodes a tree to a single string.
	string serialize(TreeNode* root) {
		vector<string> nodes;
		queue<TreeNode*> q;
		q.push(root);
		while (!q.empty()) {
			int n = q.size();
			while (n--) {
				TreeNode* cur = q.front();
				q.pop();
				if (cur == NULL)nodes.push_back("null");
				else {
					nodes.push_back(to_string(cur->val));
					q.push(cur->left);
					q.push(cur->right);
				}
			}
		}
		string ans = "";
		int n = nodes.size();
		for (int i = 0; i < n - 1; ++i) {
			ans += nodes[i] + ",";
		}
		return ans + nodes[n - 1];
	}

	// Decodes your encoded data to tree.
	TreeNode* deserialize(string data) {
		vector<string> nodes;
		int n = data.size();
		int i = 0, j = 0;
		while (i < n) {
			j = i + 1;
			while (j < n&&data[j] != ',')++j;
			string cur = data.substr(i, j - i);
			nodes.push_back(cur);
			i = j + 1;
		}

		vector<TreeNode*> tnodes;
		for (int i = 0; i < nodes.size(); ++i) {
			if (nodes[i] == "null")tnodes.push_back(NULL);
			else tnodes.push_back(new TreeNode(atoi(nodes[i].c_str())));
		}
		
		int par = 0, child = 1;
		while (child < tnodes.size()) {
			if (tnodes[par] == NULL) {
				++par;
			}
			else {
				tnodes[par]->left = tnodes[child++];
				tnodes[par++]->right = tnodes[child++];
			}
		}
		return tnodes[0];
	}
};

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

LeetCode Symmetric Tree

101. Symmetric Tree

Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).

For example, this binary tree [1,2,2,3,4,4,3] is symmetric:

    1
   / \
  2   2
 / \ / \
3  4 4  3

But the following [1,2,2,null,3,null,3] is not:

    1
   / \
  2   2
   \   \
   3    3

Follow up: Solve it both recursively and iteratively.


本题要判断一棵二叉树是否是镜面对称的,镜面对称就像题中第一个例子,在树中间画一条线,则树的左右两边是镜面对称的。
无论是递归还是迭代的解法,镜面对称的树都需要满足以下三个条件:

  1. 左右节点的值相等
  2. 左节点的左孩子等于右节点的右孩子
  3. 左节点的右孩子等于右节点的左孩子

根据以上的三条规则,可以很容易写出递归版本的代码:

class Solution {
public:
    bool work(TreeNode* left, TreeNode* right)
    {
        if (left == NULL && right == NULL)
            return true;
        if (left == NULL || right == NULL)
            return false;
        bool cond1 = left->val == right->val;
        bool cond2 = work(left->left, right->right);
        bool cond3 = work(left->right, right->left);
        return cond1 && cond2 && cond3;
    }
    bool isSymmetric(TreeNode* root)
    {
        if (root == NULL)
            return true;
        return work(root->left, root->right);
    }
};

本代码提交AC,用时6MS。
迭代的版本中,因为每层都需要判断左右是否镜面对称,所以可以用类似层次遍历的方法,也相当于广度优先遍历。因为是左右两棵子树,所以需要用到两个队列,在节点入队的时候需要注意,因为以上的第2,3个条件,所以如果对左节点的孩子入队顺序是先左后右,那么对右节点的孩子入队顺序就应该是先右后左,这样出队时的顺序才能和2,3条件一致。完整代码如下:

class Solution {
public:
    bool isSymmetric(TreeNode* root)
    {
        if (root == NULL)
            return true;
        queue<TreeNode *> left, right;
        left.push(root->left);
        right.push(root->right);
        while (!left.empty() && !right.empty()) {
            TreeNode *l = left.front(), *r = right.front();
            left.pop();
            right.pop();
            if (l == NULL && r == NULL)
                continue;
            if (l == NULL || r == NULL)
                return false;
            if (l->val != r->val)
                return false;
            left.push(l->left);
            left.push(l->right);
            right.push(r->right);
            right.push(r->left);
        }
        return true;
    }
};

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

二刷。迭代版本,用一个双端队列也可以解决,就是麻烦一点:

class Solution {
public:
	bool isSymmetric(TreeNode* root) {
		if (root == NULL)return true;
		deque<TreeNode*> q; // deque支持下标访问,而queue不支持
		q.push_back(root);
		while (!q.empty()) {
			int n = q.size();
			int i = 0, j = n - 1;
			bool curans = true;
			while (i < j) {
				if (q[i] != NULL && q[j] != NULL && q[i]->val == q[j]->val) {
					++i;
					--j;
				}
				else if (q[i] == NULL && q[j] == NULL) {
					++i;
					--j;
				}
				else {
					curans = false;
					break;
				}
			}
			if (!curans)return false;
			while (n--) {
				TreeNode* cur = q.front();
				if (cur != NULL) {
					q.push_back(cur->left);
					q.push_back(cur->right);
				}
				q.pop_front();
			}
		}
		return true;
	}
};

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

LeetCode Sqrt(x)

69. Sqrt(x)

Implement int sqrt(int x).

Compute and return the square root of x, where x is guaranteed to be a non-negative integer.

Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned.

Example 1:

Input: 4
Output: 2

Example 2:

Input: 8
Output: 2
Explanation: The square root of 8 is 2.82842..., and since 
             the decimal part is truncated, 2 is returned.

本题要对一个数开平方根。相当于LeetCode Valid Perfect Square的逆过程,很多方法都可以通用。 解法1。直接借用LeetCode Valid Perfect Square提到的牛顿法开方,代码如下:

class Solution {
public:
    int mySqrt(int x)
    {
        long long y = x;
        while (y * y > x) {
            y = (y + x / y) / 2;
        }
        return y;
    }
};

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

解法2。二分搜索,由于sqrt(x)不可能超过x/2+1(查看两个函数图像),所以可以在[0,x/2+1]范围内二分搜索,因为是向下取整,所以返回的是r,代码如下:

class Solution {
public:
    int mySqrt(int x)
    {
        long long l = 0, r = x / 2 + 1;
        while (l <= r) {
            long long mid = (l + r) / 2;
            long long prod = mid * mid;
            if (prod == x)
                return mid;
            if (prod < x)
                l = mid + 1;
            else
                r = mid – 1;
        }
        return r;
    }
};

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

三刷。暴力枚举:

class Solution {
public:
	int mySqrt(int x) {
		long long xx = x;
		if (x == 1)return 1;
		long long i = 1;
		for (; i < xx; ++i) {
			if (i*i > xx)break;
		}
		return i - 1;
	}
};

本代码提交AC,用时16MS,还是用前两个方法吧。

LeetCode Valid Perfect Square

LeetCode Valid Perfect Square Given a positive integer num, write a function which returns True if num is a perfect square else False. Note: Do not use any built-in library function such as sqrt. Example 1:

Input: 16
Returns: True
Example 2:
Input: 14
Returns: False

本题要判断一个数是否是完全平方数。有很多种解法,现列举如下: 解法1。牛顿法。牛顿法本是用来求解函数f(x)=0的根的,可以用来开方。$$f(x)=x^2-num$$等于0的正根就是num的根号。下面就是牛顿法求解f(x)=0的根的更新公式,$$x_{n+1}$$比$$x_n$$更接近f(x)=0的真实根。初始的时候可以随便代一个$$x_0$$到公式中。 对于函数$$f(x)=x^2-num$$,$$f'(x)=2x$$,所以牛顿递推式为$$!x_{n+1}=x_n-\frac{x_n^2-num}{2x_n}=(x_n+num/x_n)/2$$ 所以我们可以初始代入$$x_n=num$$,然后不断用牛顿法,直到x*x<=num时,如果x*x==num,则num为完全平方数,否则不是。 关于牛顿法用于开放的原理介绍,果壳网上有个人介绍得很详细。 代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { long long x = num; while (x*x > num) { x = (x + num / x) / 2; } return x*x == num; } }; [/cpp] 本代码提交AC,用时0MS。 解法2。对于num,如果$$x=\frac{num}{2}$$的平方依然大于num,则$$x=\frac{x}{2}$$,如果x的平方依然大于num,则继续对x除以2,不断除,直到x平方小于等于num时,遍历[x,2*x]之间的数,看看x*x==num是否成立,如果成立,说明num是完全平方数,否则不是。这种解法在高级算法里好像讲过。完整代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { if (num == 1)return true; long long x = num >> 1; while (x*x > num)x >>= 1; for (int y = x; y <= (x << 1); ++y) { if (y*y == num)return true; } return false; } }; [/cpp] 本代码提交AC,用时3MS。 解法3。观测下面的等式发现完全平方数都是由连续奇数相加而来,所以我们也可以把连续奇数加起来,直到超过num时,看看和与num是否相等。 1 = 1 4 = 1 + 3 9 = 1 + 3 + 5 16 = 1 + 3 + 5 + 7 25 = 1 + 3 + 5 + 7 + 9 36 = 1 + 3 + 5 + 7 + 9 + 11 …. 1+3+…+(2n-1) = (2n-1 + 1)n/2 = n*n 代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { long long sum = 1; for (int i = 3; sum < num; i += 2) sum += i; return sum == num; } }; [/cpp] 本代码提交AC,用时3MS。 解法4。二分搜索。l=0,r=num,mid=(l+r)/2,根据mid*mid和num的大小关系一步步缩小范围。复杂度为O(lgn),完整代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { long long l = 0, r = num; while (l <= r) { long long mid = (l + r) / 2; long long prod = mid*mid; if (prod == num)return true; if (prod < num)l = mid + 1; else r = mid – 1; } return l*l == num; } }; [/cpp] 本代码提交AC,用时0MS。 注意这个题的代码中,为了防止int越界,都需要用long long。 参考: http://bookshadow.com/weblog/2016/06/26/leetcode-valid-perfect-square/ http://www.cnblogs.com/grandyang/p/5619296.html]]>

LeetCode Longest Increasing Subsequence

300. Longest Increasing Subsequence

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. 

Note:

  • There may be more than one LIS combination, it is only necessary for you to return the length.
  • Your algorithm should run in O(n2) complexity.

Follow up: Could you improve it to O(n log n) time complexity?


给定一个数组,求最长上升子序列(Longest Increasing Subsequence, LIS)的长度。子序列可以是不连续的,和子串不一样。
此题有两种解法,一种$O(n^2)$,另一种$O(nlgn)$。
先来看$O(n^2)$。设length[i]表示到nums[i]时的LIS,那么此时已知了length[0,…,i-1],对于$j\in[0,…,i-1]$,如果nums[i]>nums[j],则在i处的LIS至少是length[j]+1,因为可以在nums[j]后面接nums[i],构成LIS,所以在i处的LIS加一。我们尝试所有的$j\in[0,…,i-1]$,找一个最大的length[j]+1赋给length[i]。
递推式如下:

length(i) =  max  { length(j) + 1 : if nums[j] < nums[i] }
           0≤j≤i-1

这种思路的代码如下,因为需要两层循环,所以时间复杂度为$O(n^2)$。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums)
    {
        int n = nums.size();
        if (n < 1)
            return 0;
        vector<int> length(n, 1);
        int ans = 1;
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j]) {
                    length[i] = max(length[i], length[j] + 1);
                }
            }
            ans = max(ans, length[i]);
        }
        return ans;
    }
};

本代码提交AC,用时33MS。
接下来看看$O(nlgn)$的解法。
对于数组d(代码中的数组nums的简称),我们定义一个辅助数组B,B[j]保存了到目前为止LIS=j时的最后一个数的最小的数,比如B[3]=8表示到目前为止长度为3的上升子序列的最后一个数的最小值为8。我们不断更新B的长度和数值,最后B的长度就是最长的上升子序列的长度,但是数组B并不是一个LIS。
举例说明。假设数组下标从1开始。数组d=[3, 5, 6, 2, 5, 4, 19, 5, 6, 7, 12],初始时B的长度为1,且B[1]=3表明此时最长上升子序列的长度为1,且该LIS的最后一个数的最小值是3。
d[2]=5,因为d[2]>B[1],所以5可以加入最长上升子序列,导致此时的最长上升子序列长度为2,且最后一个数的最小值就是5,所以B[2]=5。此时B[1]=3不变,因为长度为1的LIS的末尾数值还是3不变。B=[3,5]
d[3]=6,同上,d[3]>B[2],所以6也加入最长上升子序列,导致LIS=3,且B[3]=6B=[3,5,6]
d[4]=2,注意此时d[4]<B[3],所以2不能增加以B[3]=6结尾的最长上升子序列,所以数组B的长度不变。查找B,发现2比B[1]=3小,所以替换B[1]=3为B[1]=2,此含义为长度为1的LIS的末尾最小数由3变更为2。这很好理解,因为2自身可以作为一个LIS,且比3自身作为LIS要小。B=[2,5,6]
d[5]=5,同上,d[5]<B[3],查找B,更新B[2]=5,因为B[2]本身就是5,所以更新前后一样。B=[2,5,6]
d[6]=4,d[6]<B[3],无法增长最长上升子序列,查找B,发现4在B[1]=2和B[3]=6之间,更新B[2]=4,含义为长度为2的LIS的最后一个数的最小值由5更新为4。B=[2,4,6]。其实这样更新的目的就是为了把各个长度的LIS的末尾的最小数变小,使得后续的数更有可能接在之前LIS的末尾来增加LIS的长度。
d[7]=19,d[7]>B[3],贡献LIS长度,直接加入数组B,即B[4]=19B=[2,4,6,19]
d[8]=5,查找B,在4,19之间,所以更新B[3]=5B=[2,4,5,19]
d[9]=6,查找B,更新B[4]=6B=[2,4,5,6]
d[10]=7,d[10]>B[4],贡献LIS长度,直接加入数组B,即B[5]=7B=[2,4,5,6,7]
d[11]=12,d[11]>B[5],贡献LIS长度,直接加入数组B,即B[6]=12B=[2,4,5,6,7,12]
以上算法过程涉及到在数组B中查找当前元素d[i]的替换位置,因为数组B是有序的,所以可以使用二分查找,最终算法的时间复杂度降为了O(nlgn)。
STL中自带了在有序数组中二分查找的函数,lower_bound输入为一个数组B和目标target,返回数组B中不小于target的最小下标。我们也可以自己用二分查找实现。
这种思路的代码如下:

class Solution {
public:
    int lowerbound(vector<int>& B, int target)
    {
        int l = 0, r = B.size() – 1;
        while (l < r) {
            int m = (l + r) / 2;
            if (B[m] == target)
                return m;
            else if (B[m] > target)
                r = m – 1;
            else
                l = m + 1;
        }
        return B[l] < target ? (l + 1) : l;
    }
    int lengthOfLIS(vector<int>& nums)
    {
        int n = nums.size();
        if (n < 1)
            return 0;
        vector<int> B = { nums[0] };
        for (int i = 1; i < n; ++i) {
            if (nums[i] > B.back())
                B.push_back(nums[i]);
            else {
                //*lower_bound(B.begin(), B.end(), nums[i]) = nums[i];
                B[lowerbound(B, nums[i])] = nums[i];
            }
        }
        return B.size();
    }
};

本代码提交AC,用时3MS,速度一下提高了不少。
参考:

二刷。第二种解法,可以直接调stl的lower_bound,最好自己写,和 http://code.bitjoy.net/2020/03/21/leetcode-find-first-and-last-position-of-element-in-sorted-array/ 这题代码FindFirstIndex一致,记住二分搜索的标准写法。

class Solution {
public:
	int lengthOfLIS(vector<int>& nums) {
		int n = nums.size();
		if (n == 0)return 0;

		vector<int> curmin;
		for (int i = 0; i < n; ++i) {
			if (curmin.empty() || nums[i] > curmin.back()) {
				curmin.push_back(nums[i]);
			}
			else {

				//vector<int>::iterator it = lower_bound(curmin.begin(), curmin.end(), nums[i]);
				//*it = nums[i];

				int l = 0, r = curmin.size() - 1;
				while (l <= r) {
					int m = l + (r - l) / 2;
					if (nums[i] <= curmin[m]) {
						r = m - 1;
					}
					else {
						l = m + 1;
					}
				}
				curmin[r + 1] = nums[i];


			}
		}
		return curmin.size();
	}
};

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

LeetCode Increasing Triplet Subsequence

334. Increasing Triplet Subsequence334. Increasing Triplet Subsequence

Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the array.

Formally the function should:

Return true if there exists i, j, k
such that arr[i] < arr[j] < arr[k] given 0 ≤ i < j < k ≤ n-1 else return false.

Note: Your algorithm should run in O(n) time complexity and O(1) space complexity.

Example 1:

Input: [1,2,3,4,5]
Output: true

Example 2:

Input: [5,4,3,2,1] Output: false

问一个数组中是否有严格递增的三个数存在。
解法:令a是当前最小值,b是比a大的最小值,如果某个nums[i]>b,则a,b,nums[i]构成严格递增的三个数。
因为a,b是最小的递增二元组,如果连这样都没找到递增序列,则不可能有递增三元组了。
完整代码如下,需要注意数组中可能有相同元素,所以第一个if要用<=。

class Solution {
public:
    bool increasingTriplet(vector<int>& nums)
    {
        int a = INT_MAX, b = INT_MAX;
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] <= a) {
                // 必须<=
                a = nums[i];
            }
            else if (nums[i] < b) { // <或<=
                b = nums[i];
            }
            if (nums[i] > b) // 必须>
                return true;
        }
        return false;
    }
};

本代码提交AC,用时9MS。
二刷。首先想到了最长上升子序列,求到LIS,如果长度大于等于3的话,则肯定存在上升的3元组。代码如下:

class Solution {
private:
    int lengthOfLIS(vector<int>& nums)
    {
        int n = nums.size();
        if (n < 1)
            return 0;
        vector<int> B = { nums[0] };
        for (int i = 1; i < n; ++i) {
            if (nums[i] > B.back())
                B.push_back(nums[i]);
            else {
                *lower_bound(B.begin(), B.end(), nums[i]) = nums[i];
            }
        }
        return B.size();
    }
public:
    bool increasingTriplet(vector<int>& nums) { return lengthOfLIS(nums) >= 3; }
};

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

LeetCode Partition Equal Subset Sum

LeetCode Partition Equal Subset Sum Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal. Note:

  1. Each of the array element will not exceed 100.
  2. The array size will not exceed 200.
Example 1:
Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
Example 2:
Input: [1, 2, 3, 5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.

问一个数组能否划分成两个子数组,且这两个子数组的和相等。 如果成立,首先必须满足数组的和是偶数,然后必须能找到一个子数组的和是总和的一半。所以问题转换为从数组中取若干个元素,使得取出元素之和为target(原数组和的一半)。 用动态规划求解,令dp[j]表示能抽取一个子数组之和为j的子数组。递推式为: if dp[j] then dp[nums[i]+j]=true 含义为如果能得到子数组和为j的子数组,那么再取一个元素nums[i]扔到子数组中,就能得到子数组和为j+nums[i]的子数组。所以对于数组中的每个元素nums[i],遍历dp[0,…,target-nums[i]]应用上式。 有一点需要注意的是,遍历dp时,必须从大到小遍历,否则会出错。比如数组[1,2,5],dp[0]=1,对于元素1,如果我们从小到大遍历dp,则dp[0,…,3]都等于true;对于元素2,则会导致dp[2+2]=true。但是如果从大到小遍历dp,即dp[3,…,0],则对于元素1,只会设置dp[1]=true,最终不会导致dp[4]=true。 完整代码如下: [cpp] class Solution { public: bool canPartition(vector<int>& nums) { int sum = accumulate(nums.begin(), nums.end(), 0); if (sum & 1)return false; int target = sum / 2; vector<int> dp(target + 1); dp[0] = 1; for (int i = 0; i < nums.size(); ++i) { for (int j = target – nums[i]; j >= 0; –j) { if (dp[j])dp[j + nums[i]] = 1; } } return dp[target]; } }; [/cpp] 本代码提交AC,用时39MS。]]>