Monthly Archives: March 2017

LeetCode Count Primes

204. Count Primes

Count the number of prime numbers less than a non-negative number, n.

Example:

Input: 10
Output: 4
Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7.

本题要求从1~n共有多少个素数。Hint里一步步深入写得非常详细了,简要概括一下,有两种方法,一种是常规的从1~n一个个判断是否为非数,另一种是比较巧妙的Hash方法。 常规方法。首先看看判断一个数n是否为素数的方法,因为如果n为合数,则n可以分解为p×q,又n=p×q=(√n)×(√n),假设p是较小数的话,则p≤(√n)。所以我们只需要从2~(√n)判断能否被n整除,时间复杂度为O(n0.5)。从1~n都需要判断是否为素数,所以总的时间复杂度为O(n1.5)。 完整代码如下:

class Solution {
private:
    bool isPrime(int x)
    {
        if (x <= 1)
            return false;
        for (int i = 2; i * i <= x; ++i) {
            if (x % i == 0)
                return false;
        }
        return true;
    }
public:
    int countPrimes(int n)
    {
        int ans = 0;
        for (int i = 1; i < n; ++i) {
            if (isPrime(i))
                ++ans;
        }
        return ans;
    }
};

本代码提交TLE,在n很大的时候无法通过测试。

后来看了Hint,解法还是很巧妙的。如果一个数n是素数,则n的倍数肯定不是素数了,比如2是素数,则4=2*2、6=2*3、8=2*4…这些数肯定就不是素数了。所以我们可以建立一个1~n的Hash表,表示一个数是否为素数。初始的时候Hash[1~n]=true。然后从2开始,如果Hash[i]为true,说明i是素数,则2*i, 3*i,…都不是素数了,即Hash[2*i], Hash[3*i]..=false。如果Hash[i]为false,说明i不是素数,则i可以分解为i=p*q,使得p(或q)为素数,则之前测试p时,已经把p的所有倍数都置为false了,而i的倍数肯定也是p的倍数,所以不需要对i的倍数再置false了。

循环的终止条件是i>(√n),和第一种解法的分析一样,如果某个合数x大于(√n),则其肯定有一个素因子i是小于(√n)的,所以在测试i时,就已经把x置为false了。

另外对于素数i,我们之前分析的是要把所有2*i, 3*i…都置为false。其实这里也可以优化,我们可以从i*i开始置为false,而不需要每次都从2*i开始。比如i=5,常规是要把所有2*5, 3*5, 4*5, 5*5,…都置为false,但其实2*5, 3*5, 4*5都已经被之前的素数2或3置为false了。所以每次我们只需要从i*i开始,下标以i递增的置为false就好。(从i*i到i*i+(i-1)之间的合数被比i小的合数置为false了)总的来说,这一题加速的技巧很多,完整代码如下:

class Solution {
public:
    int countPrimes(int n)
    {
        vector<bool> mark(n, true);
        for (int i = 2; i * i < n; ++i) {
            if (!mark[i])
                continue;
            for (int j = i * i; j < n; j += i) {
                mark[j] = false;
            }
        }
        int ans = 0;
        for (int i = 2; i < n; ++i)
            ans += (mark[i] ? 1 : 0);
        return ans;
    }
};

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

LeetCode Rotate Array

189. Rotate Array

Given an array, rotate the array to the right by k steps, where k is non-negative.

Follow up:

  • Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem.
  • Could you do it in-place with O(1) extra space?

Example 1:

Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
rotate 1 steps to the right: [7,1,2,3,4,5,6]
rotate 2 steps to the right: [6,7,1,2,3,4,5]
rotate 3 steps to the right: [5,6,7,1,2,3,4]

Example 2:

Input: nums = [-1,-100,3,99], k = 2
Output: [3,99,-1,-100]
Explanation: 
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]

Constraints:

  • 1 <= nums.length <= 2 * 10^4
  • It’s guaranteed that nums[i] fits in a 32 bit-signed integer.
  • k >= 0

本题要把一个数组向右旋转k步,尽量想出3种方法。
首先,无论什么解法,都先判断一下n是否小于2,如果是的话,不管k是多少,都不必旋转了。其次,执行k%=n,因为移动n的倍数位相当于没移动。
暴力解法是每次保留最后一位数字,然后把前n-1个数往后移动一位,重复操作k次。代码如下:

class Solution {
public:
    void rotate(vector<int>& nums, int k)
    {
        int n = nums.size();
        if (n < 2 || k % n == 0)
            return;
        k %= n;
        while (k–) {
            int last = nums[n – 1];
            int j = n – 2;
            while (j >= 0) {
                nums[j + 1] = nums[j];
                –j;
            }
            nums[0] = last;
        }
    }
};

本代码提交TLE,过不了最后一个大数据集,因为最坏是O(n^2)复杂度。
稍微高明一点的方法是,先把末尾k位数保存到一个临时数组中,然后一次性把前n-k个数移动到末尾,最后再把临时数组中的数据回帖到原数组开头。完整代码如下:

class Solution {
public:
    void rotate(vector<int>& nums, int k)
    {
        int n = nums.size();
        if (n < 2 || k % n == 0)
            return;
        k %= n;
        vector<int> right(nums.begin() + n – k, nums.end());
        int i = n – k – 1, j = n – 1;
        while (i >= 0)
            nums[j–] = nums[i–];
        i = 0;
        while (i < k) {
            nums[i] = right[i];
            ++i;
        }
    }
};

本代码提交AC,用时29MS。
还可以对上述解法稍加变换,即把末尾k位数保存到临时数组right中,然后把原数组的前n-k个数拼接到right的后面,vector的拼接可以用insert。思路和上述算法是一样的。代码如下:

class Solution {
public:
    void rotate(vector<int>& nums, int k)
    {
        int n = nums.size();
        if (n < 2 || k % n == 0)
            return;
        k %= n;
        vector<int> right(nums.begin() + n – k, nums.end());
        right.reserve(n);
        right.insert(right.end(), nums.begin(), nums.begin() + n – k);
        nums = right;
    }
};

本代码提交AC,用时19MS。
最后还有一种很巧妙的方法,比如题目中的例子,要对1,2,3,4,5,6,7向右旋转3位,我们可以首先对整个数组逆序一下(reverse),编程7,6,5,4,3,2,1,然后再对前k=3,后n-k=4个数分别逆序,得到5,6,7,1,2,3,4,这不就是最终答案吗,很有意思。数组逆序可以直接用STL中的reverse,其实也可以自己实现,就是收尾元素不断交换swap。这种解法是常数空间复杂度、O(n)时间复杂度的,代码如下:

class Solution {
private:
    void my_reverse(vector<int>::iterator bg, vector<int>::iterator ed)
    {
        –ed;
        while (bg < ed) {
            swap(*bg, *ed);
            ++bg;
            –ed;
        }
    }
public:
    void rotate(vector<int>& nums, int k)
    {
        int n = nums.size();
        if (n < 2 || k % n == 0)
            return;
        k %= n;
        my_reverse(nums.begin(), nums.end());
        my_reverse(nums.begin(), nums.begin() + k);
        my_reverse(nums.begin() + k, nums.end());
    }
};

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

LeetCode Factorial Trailing Zeroes

172. Factorial Trailing Zeroes

Given an integer n, return the number of trailing zeroes in n!.

Example 1:

Input: 3
Output: 0
Explanation: 3! = 6, no trailing zero.

Example 2:

Input: 5
Output: 1
Explanation: 5! = 120, one trailing zero.

Note: Your solution should be in logarithmic time complexity.


本题要求n的阶乘结果中末尾有多少个数字0。暴力方法就是先把n!求出来,然后不断除10算末尾0的个数,但是暴力方法无法满足log(n)的时间复杂度。 参考网上的题解:http://www.geeksforgeeks.org/count-trailing-zeroes-factorial-number/,分析得还是很详细的。 要产生一个尾数0,必须是10=2*5,所以算n!中min(2的素因子,5的素因子),就有多少个0。简单考察一下5!= (2 * 2 * 2 * 3 * 5),11!= (2 8 * 34 * 52 * 7),即n!中2的素因子个数肯定要比5的素因子个数要多,这个也是可以证明的。比如从5k+1到5(k+1),只多了一个5的素因子, 但是[5k+1, 5k+2]至少多了一个2的素因子,从[5k+3, 5k+4]又至少多了一个2的素因子。所以2的素因子要多于5的素因子。
下面就把问题转换为求n!中5的素因子的个数。

举一个例子20!=1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20,能够分解出5的素因子的数只有5,10,15,20,一共4=floor(20/5)。所以20!=2432902008176640000有4个尾数0。所以n!中5的素因子的个数是floor(n/5)?

且慢,如果n=25,floor(n/5)=5,但是25=5*5能分解出两个5的素因子,所以25!中应该有六个5的素因子。所以每增加一个25的倍数的数,5的素因子的个数加1;同理每增加一个125的倍数的数,5的素因子要加2,但因为125的倍数的数肯定是25的倍数的数,所以我们在求25的倍数的数的时候已经求过一次125倍数的数的个数了,结果就是5的个数=floor(n/5)+floor(n/25)+floor(n/125)+…
完整代码如下,注意i要是long long类型的,否则可能溢出。

class Solution {
public:
    int trailingZeroes(int n)
    {
        int ans = 0;
        for (long long i = 5; n / i >= 1; i *= 5) {
            ans += n / i;
        }
        return ans;
    }
};

本代码提交AC,用时3MS。
还有一种解法,思路是一样的:

class Solution {
public:
    int trailingZeroes(int n)
    {
        int ans = 0;
        while (n > 0) {
            ans += n / 5;
            n /= 5;
        }
        return ans;
    }
};

第一种解法是固定分子不变,第二种解法是固定分母不变,结果是一样的。

LeetCode Compare Version Numbers

165. Compare Version Numbers

Compare two version numbers version1 and version2.
If version1 > version2 return 1; if version1 < version2 return -1;otherwise return 0.

You may assume that the version strings are non-empty and contain only digits and the . character.

The . character does not represent a decimal point and is used to separate number sequences.

For instance, 2.5 is not “two and a half” or “half way to version three”, it is the fifth second-level revision of the second first-level revision.

You may assume the default revision number for each level of a version number to be 0. For example, version number 3.4 has a revision number of 3 and 4 for its first and second level revision number. Its third and fourth level revision number are both 0.

Example 1:

Input: version1 = "0.1", version2 = "1.1"
Output: -1

Example 2:

Input: version1 = "1.0.1", version2 = "1"
Output: 1

Example 3:

Input: version1 = "7.5.2.4", version2 = "7.5.3"
Output: -1

Example 4:

Input: version1 = "1.01", version2 = "1.001"
Output: 0
Explanation: Ignoring leading zeroes, both “01” and “001" represent the same number “1”

Example 5:

Input: version1 = "1.0", version2 = "1.0.0"
Output: 0
Explanation: The first version number does not have a third level revision number, which means its third level revision number is default to "0"

Note:

  1. Version strings are composed of numeric strings separated by dots . and this numeric strings may have leading zeroes.
  2. Version strings do not start or end with dots, and they will not be two consecutive dots.

本题要求比较两个版本号的大小。简单题,按点.分隔,然后转换为int依次比较就好。 为了防止有的版本号没有点.,开始先给两个版本号末尾添加上一个点。另外1.0和1这两个版本号是相等的,所以如果版本位数有差别时,需要补0,而不是简单的认为长的比短的大。 完整代码如下:

class Solution {
public:
    int compareVersion(string version1, string version2)
    {
        version1 += ".";
        version2 += ".";
        size_t p1 = version1.find(‘.’), p2 = version2.find(‘.’);
        int v1, v2;
        while (p1 != string::npos || p2 != string::npos) {
            if (p1 == string::npos)
                v1 = 0;
            else {
                v1 = atoi(version1.substr(0, p1).c_str());
                version1 = version1.substr(p1 + 1);
                p1 = version1.find(‘.’);
            }
            if (p2 == string::npos)
                v2 = 0;
            else {
                v2 = atoi(version2.substr(0, p2).c_str());
                version2 = version2.substr(p2 + 1);
                p2 = version2.find(‘.’);
            }
            if (v1 < v2)
                return -1;
            else if (v1 > v2)
                return 1;
        }
        return 0;
    }
};

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

二刷。更简洁的代码:

class Solution {
private:
	void ParseVersion(const string &version, vector<int> &v) {
		int n = version.size();
		int i = 0, j = 0;
		while (i < n) {
			j = i + 1;
			while (j < n&&version[j] != '.')++j;
			int tmp = atoi(version.substr(i, j - i).c_str());
			v.push_back(tmp);
			i = j + 1;
		}
	}
public:
	int compareVersion(string version1, string version2) {
		vector<int> v1, v2;
		ParseVersion(version1, v1);
		ParseVersion(version2, v2);
		int i = 0, j = 0, n1 = v1.size(), n2 = v2.size();
		while (i < n1 || j < n2) {
			int a = (i < n1 ? v1[i] : 0);
			int b = (j < n2 ? v2[j] : 0);
			if (a > b)return 1;
			else if (a < b)return -1;
			else {
				++i;
				++j;
			}
		}
		return 0;
	}
};

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

LeetCode Maximum Product Subarray

152. Maximum Product Subarray

Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product.

Example 1:

Input: [2,3,-2,4]
Output: 6
Explanation: [2,3] has the largest product 6.

Example 2:

Input: [-2,0,-1]
Output: 0
Explanation: The result cannot be 2, because [-2,-1] is not a subarray.

本题要求一个数组中最大的连续子数组的积,和LeetCode Maximum Subarray很类似,后者是求最大连续子数组的和。
但是因为乘积有其独特性,即负负得正,所以即使前面算到有很大的负数,也不能舍弃,万一后面来一个负数,负负得正就会得到很大的正数。
本题也是使用动态规划来做。维护两个变量,令curMax和curMin分别表示当前最大连续乘积值和最小连续乘积值。则有
curMax = max(curMax*nums[i], curMin*nums[i], nums[i]);
curMin = min(curMax*nums[i], curMin*nums[i], nums[i]);
很好理解,不解释:

class Solution {
  public: int maxProduct(vector < int > & nums) {
    int ans = nums[0], curMax = nums[0], curMin = nums[0];
    for (size_t i = 1; i < nums.size(); ++i) {
      int tmp = curMax;
      curMax = max(max(curMax * nums[i], curMin * nums[i]), nums[i]);
      curMin = min(min(tmp * nums[i], curMin * nums[i]), nums[i]);
      ans = max(ans, curMax);
    }
    return ans;
  }
};

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

再细究一下,假设curMax和curMin为当前最大最小连续乘积值,且规定curMax一定是正数。则当nums[i]为正数时,curMax更新为curMax*nums[i],无论curMin是否为正,curMin都更新为curMin*nums[i]。
当nums[i]为负数时,curMax更新为curMin*nums[i],curMin更新为curMax*nums[i]。因为curMax一定为正,所以curMax*nums[i]一定为负。即使curMin也为正,但curMin<curMax,所以curMin*nums[i]>curMax*nums[i]。如果curMin为负,则curMin*nums[i]为正,更大于curMax*nums[i]了。
这种思路就分析得比较细致,注意代码第6行需要记录前一次的最大值,因为运算过程中curMax有可能是0或者负数,为了保证curMax一定是正数的性质,需要第6行(比如输入为0,2,则i=0运算完之后curMax=curMin=0;当i=1时,如果第8行curMax*=nums[i],则curMax还是0)。且因为第12行会更新curMax,而第13行需要用到上一次的最大值。
完整代码如下:

class Solution {
  public: int maxProduct(vector < int > & nums) {
    int ans = nums[0], curMax = 1, curMin = 1;
    for (size_t i = 0; i < nums.size(); ++i) {
      int oldMax = max(curMax, 1);
      if (nums[i] > 0) {
        curMax = oldMax * nums[i];
        curMin *= nums[i];
      } else {
        curMax = curMin * nums[i];
        curMin = oldMax * nums[i];
      }
      ans = max(ans, curMax);
    }
    return ans;
  }
};

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

LeetCode Min Stack

155. Min Stack

Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.

  • push(x) — Push element x onto stack.
  • pop() — Removes the element on top of the stack.
  • top() — Get the top element.
  • getMin() — Retrieve the minimum element in the stack.

Example:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> Returns -3.
minStack.pop();
minStack.top();      --> Returns 0.
minStack.getMin();   --> Returns -2.

本题要实现一个最小栈,使得push, pop, top以及getMin都是以O(1)的时间完成。

这是一个非常经典的双栈法题目,我居然不知道。同时使用两个栈,一个普通的用于存放所有数据allStack,另一个只用于存储最小值序列minStack。 push的时候,每次都把x push进allStack。如果minStack为空,则是第一次push,要把x push进minStack;如果minStack非空,则当x小于等于minStack栈顶时,可以把x push进minStack。注意当x等于minStack栈顶的时候也是需要进栈的,因为有可能有重复元素,比如对0,1,0依次进栈,遇到后面的0时,minStack只有前一个0,此时如果后一个0不进栈,如果下一步是pop的话,就会把minStack中的上一个0 pop掉,那么此时getMin时就会出错,因为此时minStack里没东西了,但allStack还保留着0和1,正确的最小值应该是前一个0。 pop时,allStack正常pop。如果pop出来的值等于minStack的栈顶,则minStack也需要pop。 top就取allStack的top。 getMin就取minStack的top。 完整代码如下:

class MinStack {
private:
    stack<int> allStack, minStack;

public: /** initialize your data structure here. */
    MinStack() {}
    void push(int x)
    {
        if (minStack.empty()) {
            minStack.push(x);
        }
        else if (x <= minStack.top()) { // 注意是<=而不是<
            minStack.push(x);
        }
        allStack.push(x);
    }
    void pop()
    {
        int top = allStack.top();
        allStack.pop();
        if (top == minStack.top()) {
            minStack.pop();
        }
    }
    int top() { return allStack.top(); }
    int getMin() { return minStack.top(); }
};

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

二刷。其实可以更简单一点,辅助的minStack大小始终和allStack一样,当新入元素小于minStack栈顶元素时,自然入minStack;否则,就把minStack栈顶的元素再入一次栈。这样就使得两个栈的大小一样,pop的时候可以同时pop,减少了各种if判断。
完整代码如下:

class MinStack {
private:
    stack<int> allStack, minStack;
public: /** initialize your data structure here. */
    MinStack() {}
    void push(int x)
    {
        if (minStack.empty() || x < minStack.top()) {
            minStack.push(x);
        }
        else {
            minStack.push(minStack.top());
        }
        allStack.push(x);
    }
    void pop()
    {
        allStack.pop();
        minStack.pop();
    }
    int top() { return allStack.top(); }
    int getMin() { return minStack.top(); }
};

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

LeetCode Evaluate Reverse Polish Notation

150. Evaluate Reverse Polish Notation

Evaluate the value of an arithmetic expression in Reverse Polish Notation.

Valid operators are +-*/. Each operand may be an integer or another expression.

Note:

  • Division between two integers should truncate toward zero.
  • The given RPN expression is always valid. That means the expression would always evaluate to a result and there won’t be any divide by zero operation.

Example 1:

Input: ["2", "1", "+", "3", "*"]
Output: 9
Explanation: ((2 + 1) * 3) = 9

Example 2:

Input: ["4", "13", "5", "/", "+"]
Output: 6
Explanation: (4 + (13 / 5)) = 6

Example 3:

Input: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
Output: 22
Explanation: 
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

本题要对一个逆波兰表达式求值。简单题,借助一个堆栈,遇到数字压栈,遇到操作符,把栈顶两个元素弹栈并进行运算。需要注意的是,假设先后弹出来的两个元素是v1,v2,则后续的所有操作都应该是v2 op v1,不要搞错了。 完整代码如下:

class Solution {
public:
    int evalRPN(vector<string>& tokens)
    {
        stack<int> expression;
        for (size_t i = 0; i < tokens.size(); ++i) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                int v1 = expression.top();
                expression.pop();
                int v2 = expression.top();
                expression.pop();
                if (tokens[i] == "+")
                    expression.push(v2 + v1);
                else if (tokens[i] == "-")
                    expression.push(v2 – v1);
                else if (tokens[i] == "*")
                    expression.push(v2 * v1);
                else if (tokens[i] == "/")
                    expression.push(v2 / v1);
            }
            else
                expression.push(atoi(tokens[i].c_str()));
        }
        return expression.top();
    }
};

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

LeetCode Sort List

148. Sort List

Sort a linked list in O(n log n) time using constant space complexity.

Example 1:

Input: 4->2->1->3
Output: 1->2->3->4

Example 2:

Input: -1->5->3->4->0
Output: -1->0->3->4->5

本题要求用O(nlogn)的时间复杂度和常数空间复杂度对一个链表排序。 O(nlgn)的排序算法有快速排序、堆排序、归并排序,快排和堆排都需要对元素的随机访问,不适用于链表,这里我们可以用归并排序实现。 归并的思路是不断把链表对分成两个子链表,直到只剩一个节点,此时一个节点是有序的,然后不断的两两归并,mergeList就好办多了。 完整代码如下:

class Solution {
private:
    ListNode* mergeList(ListNode* h1, ListNode* h2)
    {
        if (!h1)
            return h2;
        else if (!h2)
            return h1;
        ListNode *head = new ListNode(0), *h = head;
        while (h1 && h2) {
            if (h1->val < h2->val) {
                h->next = h1;
                h1 = h1->next;
            }
            else {
                h->next = h2;
                h2 = h2->next;
            }
            h = h->next;
        }
        if (h1)
            h->next = h1;
        else if (h2)
            h->next = h2;
        return head->next;
    }
public:
    ListNode* sortList(ListNode* head)
    {
        if (!head || !head->next)
            return head;
        ListNode *slow = head, *fast = head;
        while (fast->next && fast->next->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        fast = slow->next;
        slow->next = NULL;
        ListNode* left = sortList(head);
        ListNode* right = sortList(fast);
        return mergeList(left, right);
    }
};

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

二刷,归并排序还可以写得简单点:

class Solution {
public:
	ListNode* sortList(ListNode* head) {
		if (head == NULL || head->next == NULL)return head;
		ListNode *slow = head, *fast = head, *pre = head;
		while (fast != NULL && fast->next != NULL) {
			pre = slow;
			slow = slow->next;
			fast = fast->next->next;
		}
		pre->next = NULL;
		ListNode *left = sortList(head), *right = sortList(slow);
		ListNode *dummy = new ListNode(0);
		ListNode *lst = dummy;
		while (left != NULL || right != NULL) {
			int l = left != NULL ? left->val : INT_MAX;
			int r = right != NULL ? right->val : INT_MAX;
			if (l < r) {
				lst->next = left;
				left = left->next;
			}
			else {
				lst->next = right;
				right = right->next;
			}
			lst = lst->next;
		}
		return dummy->next;
	}
};

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

LeetCode Reorder List

143. Reorder List

Given a singly linked list LL0L1→…→Ln-1Ln,
reorder it to: L0LnL1Ln-1L2Ln-2→…

You may not modify the values in the list’s nodes, only nodes itself may be changed.

Example 1:

Given 1->2->3->4, reorder it to 1->4->2->3.

Example 2:

Given 1->2->3->4->5, reorder it to 1->5->2->4->3.

本题要求把单链表重新排序,规则是第一个节点接最后一个节点,第二个节点接倒数第二个节点,以此类推。要求in-place,不能借助额外的空间。 这个题我一开始想到的是递归解法,就是首先把L0和Ln接好,然后递归的在L1->…->L(n-1)重排序。但是这样的算法性能肯定很差,因为首先要找到尾节点就很麻烦,还要递归的找尾节点。

看了网上的题解,发现真是巧妙。把链表重排序之后的结果其实相当于先把链表从中点分成前后两段,然后把后半段链表逆序,最后合并两个链表,合并的时候交替从两个链表中取节点拼接起来。 举个例子,0->1->2->3->4,中点是2,分成两段之后就是0->1和2->3->4,然后把第二个链表逆序为4->3->2,最后交替拼接这两个链表,结果就是0->4->1->3->2。、 所以知道思路之后就好办了,找中点可以用快慢指针法,链表逆序用头插法,最后拼接也不是什么难事,不过整合起来代码稍微有点长。 完整代码如下:

class Solution {
public:
    void reorderList(ListNode* head)
    {
        if (head == NULL || head->next == NULL || head->next->next == NULL)
            return; // find mid node
        ListNode *slow = head, *fast = head, *pre = slow;
        while (slow->next != NULL && fast != NULL && fast->next != NULL) {
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        pre->next = NULL;
        // reverse [mid,end]
        ListNode *par = new ListNode(0), *tail;
        while (slow) {
            tail = slow->next;
            slow->next = par->next;
            par->next = slow;
            slow = tail;
        }
        //merge 2 list
        par = par->next;
        ListNode *new_head = new ListNode(0), *h = new_head;
        while (head || par) {
            if (head == NULL) {
                h->next = par;
                par = par->next;
            }
            else {
                h->next = head;
                head = head->next;
                h = h->next;
                h->next = par;
                par = par->next;
            }
            h = h->next;
        }
        head = new_head->next;
    }
};

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

LeetCode Copy List with Random Pointer

138. Copy List with Random Pointer

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.

Return a deep copy of the list.

The Linked List is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:

  • val: an integer representing Node.val
  • random_index: the index of the node (range from 0 to n-1) where random pointer points to, or null if it does not point to any node.

Example 1:

Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]

Example 2:

Input: head = [[1,1],[2,1]]
Output: [[1,1],[2,1]]

Example 3:

Input: head = [[3,null],[3,0],[3,null]]
Output: [[3,null],[3,0],[3,null]]

Example 4:

Input: head = []
Output: []
Explanation: Given linked list is empty (null pointer), so return null.

Constraints:

  • -10000 <= Node.val <= 10000
  • Node.random is null or pointing to a node in the linked list.
  • Number of Nodes will not exceed 1000.

本题要求对一个链表进行深度拷贝。这个链表有一个特点就是除了有value和next指针,还有一个random指针随机指向链表中的一个节点。 我的解法是这样的,使用一个hash表记录每个节点的下标,并且用一个数组array_for_list存储新创建的链表。第一遍扫描只把value和next都复制好,同时记录hash和array_for_list。第二遍扫描时,看看原节点的random是否为空,如果不为空,则去hash表里找其random指向节点的下标pos,同时把新链表的random指向array_for_list[pos]节点。
完整代码如下:

class Solution {
public:
    RandomListNode* copyRandomList(RandomListNode* head)
    {
        unordered_map<RandomListNode*, int> hash;
        vector<RandomListNode*> array_for_list;
        RandomListNode *new_head = new RandomListNode(0), *h1 = head, *h2 = new_head;
        int idx = 0;
        while (h1) {
            hash[h1] = idx++;
            RandomListNode* node = new RandomListNode(h1->label);
            array_for_list.push_back(node);
            h2->next = node;
            h1 = h1->next;
            h2 = h2->next;
        }
        h1 = head;
        h2 = new_head->next;
        while (h1) {
            if (h1->random) {
                h2->random = array_for_list[hash[h1->random]];
            }
            h1 = h1->next;
            h2 = h2->next;
        }
        return new_head->next;
    }
};

本代码提交AC,用时52MS.

二刷。《剑指offer》上有一个类似的题,可以不用额外空间来做,即先对原数组的每个节点都拷贝一个插到原节点的后面,然后对于每一个原节点,新节点的random节点就是原节点random节点的下一个节点。很有意思,可以看《剑指offer》P147,或者看讨论区:https://discuss.leetcode.com/topic/7594/a-solution-with-constant-space-complexity-o-1-and-linear-time-complexity-o-n/64
完整代码如下:

class Solution {
public:
    RandomListNode* copyRandomList(RandomListNode* head)
    {
        if (head == NULL)
            return head;
        RandomListNode* iter = head;
        while (iter) {
            RandomListNode* node = new RandomListNode(iter->label);
            node->next = iter->next;
            iter->next = node;
            iter = node->next;
        }
        iter = head;
        while (iter) {
            if (iter->random) {
                iter->next->random = iter->random->next;
            }
            iter = iter->next->next;
        }
        iter = head;
        RandomListNode* copy_head = new RandomListNode(0); // dummy
        RandomListNode *copy_iter = iter->next, *copy_tail = copy_head;
        while (iter) {
            copy_tail->next = copy_iter;
            copy_tail = copy_tail->next;
            iter->next = copy_iter->next;
            iter = iter->next;
            if (iter) {
                copy_iter->next = iter->next;
                copy_iter = copy_iter->next;
            }
        }
        return copy_head->next;
    }
};

本代码提交AC,用时65MS,居然比前面的还慢。。。

三刷。和上面的解法相同,代码更易读:

class Solution {
public:
	Node* copyRandomList(Node* head) {
		if (head == NULL)return NULL;

		// 拷贝主干节点
		Node *l1 = head;
		while (l1 != NULL) {
			Node *cur = new Node(l1->val);
			cur->next = l1->next;
			l1->next = cur;
			l1 = cur->next;
		}

		//连接random指针
		l1 = head;
		while (l1 != NULL) {
			if (l1->random != NULL) {
				l1->next->random = l1->random->next;
			}
			l1 = l1->next->next;
		}

		//拆分
		l1 = head;
		Node *new_head = l1->next;
		Node *l2 = new_head;
		while (l1 != NULL) {
			l1->next = l2->next;
			if (l1->next != NULL) {
				l2->next = l1->next->next;
			}
			l1 = l1->next;
			l2 = l2->next;
		}
		return new_head;
	}
};

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