Tag Archives: 排序

LeetCode Queue Reconstruction by Height

LeetCode Queue Reconstruction by Height Suppose you have a random list of people standing in a queue. Each person is described by a pair of integers (h, k), where h is the height of the person and k is the number of people in front of this person who have a height greater than or equal to h. Write an algorithm to reconstruct the queue. Note: The number of people is less than 1,100. Example

Input:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
Output:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

本题给了一个存储pair的数组,每个pair是一个(h,k)对,表示身高为h的人,其前面有k个身高大于等于h的人。现在要对这个数组重新排序,使得所有pair都满足其自己的(h,k)约束。 网上有一种很巧妙的方法,先对数组排序,排序规则是h大的在前,如果h相等,则k小的在前。然后新建一个数组,把每个pair插入到下标k的位置。 我们来举个例子,比如样例数据,先按上述规则排序之后变成了: [7,0], [7,1], [6,1], [5,0], [5,2], [4,4] 上述排序规则基于这样一个事实:h越小的,其前面比h大的数越多,越有可能满足其k约束;h相同时,肯定是k小的排前面,因为相同的h对k是有贡献的。 然后新建一个空的数组,不断把上述排好序的元素插入空数组中。
  1. [7,0]插入第0个位置,数组为[7,0]
  2. [7,1]插入第1个位置,数组为[7,0], [7,1]
  3. [6,1]插入第1个位置,数组为[7,0], [6,1], [7,1]
  4. [5,0]插入第0个位置,数组为[5,0], [7,0], [6,1], [7,1]
  5. [5,2]插入第2个位置,数组为[5,0], [7,0], [5,2], [6,1], [7,1]
  6. [4,4]插入第4个位置,数组为[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]
完整代码如下: [cpp] bool comp(const pair<int, int>& p1, const pair<int, int>& p2) { return p1.first > p2.first || (p1.first == p2.first&&p1.second < p2.second); } class Solution { public: vector<pair<int, int>> reconstructQueue(vector<pair<int, int>>& people) { sort(people.begin(), people.end(), comp); vector<pair<int, int>> ans; for (int i = 0; i < people.size(); ++i) { ans.insert(ans.begin() + people[i].second, people[i]); } return ans; } }; [/cpp] 本代码提交AC,用时49MS。]]>

LeetCode Relative Ranks

LeetCode Relative Ranks Given scores of N athletes, find their relative ranks and the people with the top three highest scores, who will be awarded medals: “Gold Medal”, “Silver Medal” and “Bronze Medal”. Example 1:

Input: [5, 4, 3, 2, 1]
Output: ["Gold Medal", "Silver Medal", "Bronze Medal", "4", "5"]
Explanation: The first three athletes got the top three highest scores, so they got "Gold Medal", "Silver Medal" and "Bronze Medal".
For the left two athletes, you just need to output their relative ranks according to their scores.
Note:
  1. N is a positive integer and won’t exceed 10,000.
  2. All the scores of athletes are guaranteed to be unique.

给定N个运动员的得分,要求输出他们的相对排序,前三名还需要颁发金银铜奖牌。 简单题,构造分数和下标的pair,然后对他们排序,得到相对排名之后,转换为string输出。 代码如下: [cpp] class Solution { public: vector<string> findRelativeRanks(vector<int>& nums) { vector<pair<int, int>> scores; int n = nums.size(); for (int i = 0; i < n; ++i)scores.push_back(pair<int, int>(nums[i], i)); sort(scores.begin(), scores.end(), greater<pair<int,int>>()); vector<string> ans(n, ""); vector<string> medals = { "Gold Medal", "Silver Medal", "Bronze Medal" }; for (int i = 0; i < min(3, n); ++i)ans[scores[i].second] = medals[i]; for (int i = 3; i < n; ++i)ans[scores[i].second] = to_string(i + 1); return ans; } }; [/cpp] 本代码提交AC,用时13MS。]]>

LeetCode Minimum Moves to Equal Array Elements II

LeetCode Minimum Moves to Equal Array Elements II Given a non-empty integer array, find the minimum number of moves required to make all array elements equal, where a move is incrementing a selected element by 1 or decrementing a selected element by 1. You may assume the array’s length is at most 10,000. Example:

Input:
[1,2,3]
Output:
2
Explanation:
Only two moves are needed (remember each move increments or decrements one element):
[1,2,3]  =>  [2,2,3]  =>  [2,2,2]

LeetCode Minimum Moves to Equal Array Elements的改编题。这次每次只能改变一个数,让其增大1或者减小1,问最小需要多少次操作能使所有数字相等。 其实看样例就能猜到,最终统一的那个数是原数组中的中位数。这就好办了,对原数组排个序,然后累加所有元素和中位数的绝对值就可以了。 代码如下: [cpp] class Solution { public: int minMoves2(vector<int>& nums) { sort(nums.begin(), nums.end()); int ans = 0, mid = nums[nums.size() / 2]; for (const int& i : nums)ans += abs(i – mid); return ans; } }; [/cpp] 本代码提交AC,用时22MS。 排好序之后还有另一种方法求操作数,比如排好序是1,3,5,7,9,最终所有数都要变成5。之前的做法是所有数和5作差求绝对值加和,现在可以这样做,1到5需要(5-1)次操作,9变成5需要(9-5)次操作,累加就是(5-1)+(9-5)=(9-1),所以我们取排序之后的首位元素,互相作差即可。这就看起来和中位数没关系了。 代码如下: [cpp] class Solution { public: int minMoves2(vector<int>& nums) { sort(nums.begin(), nums.end()); int ans = 0, i = 0, j = nums.size() – 1; while (i < j) { ans += nums[j–] – nums[i++]; } return ans; } }; [/cpp] 本代码提交AC,用时19MS。 既然是要求中位数,其实不用对数组排序都可以做到,就是之前的求第k大数LeetCode Kth Largest Element in an Array,直接借用那个函数即可。STL也有类似的函数nth_element。 [cpp] class Solution { private: int helper(vector<int>& nums, int left, int right, int k) { if (left == right)return nums[left]; int pivot = nums[left]; int l = left, r = right; while (l < r) { while (l < r&&nums[r] <= pivot)–r; if (l >= r)break; nums[l] = nums[r]; while (l < r&&nums[l] >= pivot)++l; if (l >= r)break; nums[r] = nums[l]; } nums[l] = pivot; if (l + 1 == k)return pivot; if (k < l + 1)return helper(nums, left, l – 1, k); else return helper(nums, l + 1, right, k); } public: int minMoves2(vector<int>& nums) { //int ans = 0, mid = helper(nums, 0, nums.size() – 1, nums.size() / 2 + 1); nth_element(nums.begin(), nums.begin() + nums.size() / 2, nums.end()); int ans = 0, mid = nums[nums.size() / 2]; for (const int& i : nums)ans += abs(i – mid); return ans; } }; [/cpp] 本代码提交AC,用时13MS。]]>

LeetCode Kth Largest Element in an Array

215. Kth Largest Element in an Array215. Kth Largest Element in an Array

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Example 1:

Input: [3,2,1,5,6,4] and k = 2
Output: 5

Example 2:

Input: [3,2,3,1,2,4,5,5,6] and k = 4
Output: 4

Note:
You may assume k is always valid, 1 ≤ k ≤ array’s length.215. Kth Largest Element in an Array


本题要求一个未排序数组中第k大的数。
解法1,直接从大到小排序,然后取第k个数,时间复杂度O(nlgn)。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k)
    {
        sort(nums.begin(), nums.end(), std::greater<int>());
        return nums[k – 1];
    }
};

本代码提交AC,用时16MS。
解法2,借助快排的思路,每次选择一个枢纽pivot,把区间[left,right]划分为比pivot大和小的两个部分,然后就能确定pivot的最终位置,如果其下标正好是k-1,则说明pivot就是第k大的数。否则可以判断第k大的数在左半边还是右半边,然后递归在其中一半查找第k大的数。时间复杂度可以由T(n)=T(n/2)+O(n)解出是O(n),注意我们只需要在其中一半递归,所以是加一个T(n/2)。
代码如下:

class Solution {
private:
    int helper(vector<int>& nums, int left, int right, int k)
    {
        if (left == right)
            return nums[left];
        int pivot = nums[left];
        int l = left, r = right;
        while (l < r) {
            while (l < r && nums[r] <= pivot)
                –r;
            if (l >= r)
                break;
            nums[l] = nums[r];
            while (l < r && nums[l] >= pivot)
                ++l;
            if (l >= r)
                break;
            nums[r] = nums[l];
        }
        nums[l] = pivot;
        if (l + 1 == k)
            return pivot;
        if (k < l + 1)
            return helper(nums, left, l – 1, k);
        else
            return helper(nums, l + 1, right, k);
    }
public:
    int findKthLargest(vector<int>& nums, int k) { return helper(nums, 0, nums.size() – 1, k); }
};

本代码提交AC,用时39MS,居然比直接排序还慢。。。
解法3,使用堆排序。对原数组建一个最大堆,然后不断的把前k-1个堆顶弹出,下一个堆顶元素就是第k大的元素。建堆时间为O(n),维护堆的性质需要O(lgn)时间,所以总时间复杂度为O(klgn+n)。
代码如下:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k)
    {
        priority_queue<int> pq(nums.begin(), nums.end());
        for (int i = 0; i < k – 1; ++i)
            pq.pop();
        return pq.top();
    }
};

本代码提交AC,用时9MS,是最快的。
二刷。还是使用快排划分的方法,但是这次把partition的过程单独抽出来了,逻辑更清晰,代码如下:

class Solution {
private:
    int partition(vector<int>& nums, int start, int end)
    {
        if (start >= end)
            return start;
        int i = start, j = end, pivot = nums[start];
        while (i < j) {
            while (i < j && nums[j] <= pivot)
                –j; // 注意符号,从大到小排序
            if (i < j) {
                nums[i] = nums[j];
                ++i;
            }
            while (i < j && nums[i] > pivot)
                ++i; // 注意符号,从大到小排序
            if (i < j) {
                nums[j] = nums[i];
                –j;
            }
        }
        nums[i] = pivot;
        return i;
    }
public:
    int findKthLargest(vector<int>& nums, int k)
    {
        int start = 0, end = nums.size() – 1;
        while (true) {
            int p = partition(nums, start, end);
            if (p + 1 == k)
                return nums[p];
            else if (p + 1 < k)
                start = p + 1;
            else
                end = p – 1;
        }
        return -1;
    }
};

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

三刷。更简洁不易出错的快排划分方法:

class Solution {
private:
	int FindK(vector<int>& nums, int left, int right, int k) {
		int pivot = nums[right];
		int next_idx = left;
		for (int i = left; i <= right; ++i) {
			if (nums[i] > pivot) {
				swap(nums[i], nums[next_idx++]);
			}
		}
		swap(nums[right], nums[next_idx]);
		int rank = next_idx + 1 - left;
		if (rank == k)return pivot;
		else if (k < rank)return FindK(nums, left, next_idx - 1, k);
		else return FindK(nums, next_idx + 1, right, k - rank);
	}
public:
	int findKthLargest(vector<int>& nums, int k) {
		return FindK(nums, 0, nums.size() - 1, k);
	}
};

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

LeetCode Largest Number

179. Largest Number

Given a list of non negative integers, arrange them such that they form the largest number.

Example 1:

Input: [10,2]
Output: "210"

Example 2:

Input: [3,30,34,5,9]
Output: "9534330"

Note: The result may be very large, so you need to return a string instead of an integer.


给定一个数组,要把数组中所有的数拼成一个长的数,问能够拼成的最大的数是多少,要求返回这个最大数的字符串形式。

要想长数越大,则数组中“越大的数”应该排在前面,这里的“大”是指字典序,比如9就比34大,所以9应该在字符串中排前面。所以大的思路是先把数字数组转换为对应的字符串数组,然后按字典序对字符串数组排序,最后按先后顺序拼接起来。

还有一个问题是,遇到30和34时,应该把34排在30的前面,即3430>3034,这符合字典序,没什么问题。

另一个问题是,遇到一个数是另一个数的前缀的情况,应该怎么排序呢,比如34和349时,应该是349排在34的前面,因为34934>34349,这也符合字典序。但是,如果是34和341,正确的排序应该是34排在341的前面,因为34341>34134,此时就不符合字典的降序了。所以需要自定义一个对字符串的排序算法,此算法要对字符串s1和s2进行比较,方法是判断s1+s2和s2+s1的字典序,因为这就是这两个字符串按不同顺序拼接之后的大小。

最后一个问题,如果遇到数组是[0,0],即两个0时,能拼成的最大数是0,但是如果先转换成字符数组之后,拼成的数就是00了。所以在最后,我们判断一下开头的字符是否为’0’,如果是,说明这个数就是0了,不管后面是否还有多余的字符’0’,我们都只返回”0″。如果开头的字符不是’0’,则多个’0’是有效的,比如30和300是不同的两个数。

完整代码如下:

bool comp(const string& s1, const string& s2)
{
    string tmp1 = s1 + s2, tmp2 = s2 + s1;
    return tmp1 > tmp2;
}
class Solution {
public:
    string largestNumber(vector<int>& nums)
    {
        vector<string> snums;
        for (size_t i = 0; i < nums.size(); ++i)
            snums.push_back(to_string(nums[i]));
        sort(snums.begin(), snums.end(), comp);
        string ans = "";
        for (size_t i = 0; i < snums.size(); ++i)
            ans += snums[i];
        if (!ans.empty() && ans[0] == ‘0’)
            return "0";
        return ans;
    }
};

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

代码中注意一点,自定义的比较函数不能是类的成员函数,要不然会报错“非标准语法;请使用 “&” 来创建指向成员的指针”,这个错误提示很奇怪。后来才知道比较函数必须定义成全局函数或者静态函数,因为sort自己就是全局或静态函数,无法调用成员函数

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 Insertion Sort List

147. Insertion Sort List

Sort a linked list using insertion sort.


A graphical example of insertion sort. The partial sorted list (black) initially contains only the first element in the list.
With each iteration one element (red) is removed from the input data and inserted in-place into the sorted list
 

Algorithm of Insertion Sort:

  1. Insertion sort iterates, consuming one input element each repetition, and growing a sorted output list.
  2. At each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there.
  3. It repeats until no input elements remain.


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

对一个链表进行插入排序。 简单题,每次从原链表中断一个节点下来,然后在新的有序链表中线性查找插入的位置。这里有个技巧,有可能链表的第一个节点很大,后续节点需要插入表头;也可能后续节点很大,需要插入表尾;还可能是不大不小,插入中间。情况比较多,为了方便统一代码形式,可以在新的有序链表的表头插入一个INT_MIN的最小节点,这样所有代码都统一了,也不容易出错。 完整代码如下:

class Solution {
public:
    ListNode* insertionSortList(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode* newHead = new ListNode(INT_MIN);
        newHead->next = head;
        head = head->next;
        newHead->next->next = NULL;
        while (head) {
            ListNode *insertPos = newHead->next, *pre = newHead;
            while (insertPos && insertPos->val < head->val) {
                insertPos = insertPos->next;
                pre = pre->next;
            }
            ListNode* tmp = head;
            head = head->next;
            tmp->next = insertPos;
            pre->next = tmp;
        }
        return newHead->next;
    }
};

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

二刷。需要注意如果测试数据中含有INT_MIN的情况:

class Solution {
public:
	ListNode* insertionSortList(ListNode* head) {
		if (head == NULL || head->next == NULL)return head;
		ListNode *dummy = new ListNode(INT_MIN);
		ListNode *l1 = head, *l2 = head;
		while (l1 != NULL) {
			l2 = l1->next;

			ListNode *l3 = dummy, *l3_pre = dummy;
			while (l3 != NULL && l1->val > l3->val) {
				l3_pre = l3;
				l3 = l3->next;
			}
			if (l1->val == INT_MIN) {
				l1->next = dummy->next;
				dummy->next = l1;
			}
			else {
				l1->next = l3;
				l3_pre->next = l1;
			}

			l1 = l2;
		}
		return dummy->next;
	}
};

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

LeetCode Insert Interval

57. Insert Interval

Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary).

You may assume that the intervals were initially sorted according to their start times.

Example 1:

Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]

Example 2:

Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
Output: [[1,2],[3,10],[12,16]]
Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].

NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature.


一个已经排好序的区间数组,现在要插入一个新的区间,并且如果能合并则需要合并。最简单的方法就是把新加入的区间和原有区间一起排个序,然后统一合并,问题就转换为LeetCode Merge Intervals了。

但是原有区间数组是已经按start排序了,所以有更简单的办法。我们可以分三个过程插入新的区间,首先把明显小于新区间的区间放到结果数组中,然后处理所有可能和新区间有overlap的区间,不断合并并更新新区间,直到无法再合并时,把新区间加入结果数组中,最后把明显大于新区间的区间放到结果数组中。

完整代码如下:

class Solution {
public:
    vector<Interval> insert(vector<Interval>& intervals, Interval newInterval)
    {
        vector<Interval> ans;
        int i = 0, n = intervals.size();
        while (i < n && intervals[i].end < newInterval.start)
            ans.push_back(intervals[i++]);
        while (i < n && newInterval.end >= intervals[i].start) {
            newInterval.start = min(newInterval.start, intervals[i].start);
            newInterval.end = max(newInterval.end, intervals[i].end);
            ++i;
        }
        ans.push_back(newInterval);
        while (i < n)
            ans.push_back(intervals[i++]);
        return ans;
    }
};

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

LeetCode Merge Intervals

56. Merge Intervals

Given a collection of intervals, merge all overlapping intervals.

Example 1:

Input: [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].

Example 2:

Input: [[1,4],[4,5]]
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.

NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature.


给定一个区间数组,也就是数组中保存的是一个个的区间[s,t],要求把数组中所有有overlap的区间合并起来。
简单题,先给数组排个序,然后只要看相邻的两个区间是否有重复就好了。排序的规则是先比较start,如果start相等,再比较end。
其实这个区间完全可以用pair来存,但是题目用一个自定义的Interval结构体存储。有两种方法可以对自定义结构体(或类)排序,一是重载Interval的小于<比较运算符,这样就可以直接sort(vector.begin(),vector.end())了,但是这样改变了Interval;还有一种方法是不改变Interval,自定义一个comp比较函数,传递给sort算法的第三个参数。
代码如下:

bool comp(const Interval& i, const Interval& j)
{
    return (i.start < j.start) || ((i.start == j.start) && (i.end < j.end));
}
class Solution {
public:
    vector<Interval> merge(vector<Interval>& intervals)
    {
        if (intervals.size() == 0)
            return vector<Interval>();
        sort(intervals.begin(), intervals.end(), comp);
        vector<Interval> ans = { intervals[0] };
        for (int i = 1; i < intervals.size(); ++i) {
            if (intervals[i].start <= ans.back().end) {
                ans.back().end = max(ans.back().end, intervals[i].end);
            }
            else {
                ans.push_back(intervals[i]);
            }
        }
        return ans;
    }
};

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

二刷。想到用栈来合并区间,代码如下:

class Solution {
public:
	bool IsOverlap(const pair<int, int>& interval1, const pair<int, int>& interval2) {
		return interval1.second >= interval2.first;
	}
	vector<vector<int>> merge(vector<vector<int>>& intervals) {
		vector<pair<int, int>> intervals2;
		for (int i = 0; i < intervals.size(); ++i) {
			intervals2.push_back(make_pair(intervals[i][0], intervals[i][1]));
		}
		sort(intervals2.begin(), intervals2.end());
		vector<vector<int>> ans;
		stack<pair<int, int>> stk;
		for (int i = 0; i < intervals2.size(); ++i) {
			if (stk.empty()) {
				stk.push(intervals2[i]);
			}
			else {
				pair<int, int> top = stk.top();
				stk.pop();
				if (IsOverlap(top, intervals2[i])) {
					stk.push(make_pair(top.first, max(top.second, intervals2[i].second)));
				}
				else {
					ans.push_back({ top.first,top.second });
					stk.push({ intervals2[i].first,intervals2[i].second });
				}
			}
		}
		if (!stk.empty())ans.push_back({ stk.top().first,stk.top().second });
		return ans;
	}
};

本代码提交AC,用时24MS,效率不如第一种方法高。

LeetCode 4Sum

18. 4Sum

Given an array nums of n integers and an integer target, are there elements abc, and d in nums such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.

Note:

The solution set must not contain duplicate quadruplets.

Example:

Given array nums = [1, 0, -1, 0, -2, 2], and target = 0.

A solution set is:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

本题问一个数组中哪四个数加起来的和等于target,相当于LeetCode 3Sum的升级版,但是方法和3Sum是一样的,先枚举前两个数,对于后两个数,用首尾指针来求和。 一开始我的代码完全按照LeetCode 3Sum来写,先求最大最小值,然后用桶来hash,最后三层循环,代码如下:

class Solution {
public:
    vector<vector<int> > fourSum(vector<int>& nums, int target)
    {
        int min_v = INT_MAX, max_v = INT_MIN;
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] < min_v)
                min_v = nums[i];
            if (nums[i] > max_v)
                max_v = nums[i];
        }
        vector<int> hash(max_v – min_v + 1, 0);
        for (int i = 0; i < nums.size(); ++i) {
            ++hash[nums[i] – min_v];
        }
        vector<vector<int> > ans;
        for (int i = 0; i < hash.size(); ++i) {
            if (hash[i] == 0)
                continue;
            –hash[i];
            for (int j = i; j < hash.size(); ++j) {
                if (hash[j] == 0)
                    continue;
                –hash[j];
                int u = j, v = hash.size() – 1;
                while (u <= v) {
                    while (u <= v && hash[u] == 0)
                        ++u;
                    if (u > v)
                        break;
                    –hash[u];
                    while (u <= v && hash[v] == 0)
                        –v;
                    if (u > v) {
                        ++hash[u];
                        break;
                    }
                    –hash[v];
                    int sum = i + j + u + v + 4 * min_v;
                    ++hash[u];
                    ++hash[v];
                    if (sum == target) {
                        ans.push_back({ i + min_v, j + min_v, u + min_v, v + min_v });
                        ++u;
                        –v;
                    }
                    else if (sum < target)
                        ++u;
                    else
                        –v;
                }
                ++hash[j];
            }
            ++hash[i];
        }
        return ans;
    }
};

但是本代码提交TLE,看了一下,最后一个样例中,最小值有-236727523之小,最大值有91277418之大,导致hash数组太大了,从而无效的空for循环太多,最终TLE。
后来改用排序代替hash,需要注意为了防止重复结果,对四个数都要有去重的操作。代码如下:

class Solution {
public:
    vector<vector<int> > fourSum(vector<int>& nums, int target)
    {
        sort(nums.begin(), nums.end());
        vector<vector<int> > ans;
        int n = nums.size();
        for (int i = 0; i < n – 3; ++i) {
            if (i > 0 && nums[i] == nums[i - 1])
                continue; // 防止nums[i]重复
            for (int j = i + 1; j < n – 2; ++j) {
                if (j > i + 1 && nums[j] == nums[j - 1])
                    continue; // 防止nums[j]重复
                int u = j + 1, v = n – 1;
                while (u < v) {
                    int sum = nums[i] + nums[j] + nums[u] + nums[v];
                    if (sum == target) {
                        ans.push_back({ nums[i], nums[j], nums[u], nums[v] });
                        int k = u + 1;
                        while (k < v && nums[k] == nums[u])
                            ++k; // 防止nums[u]重复
                        u = k;
                        k = v – 1;
                        while (k > u && nums[k] == nums[v])
                            –k; // 防止nums[v]重复
                        v = k;
                    }
                    else if (sum < target)
                        ++u;
                    else
                        –v;
                }
            }
        }
        return ans;
    }
};

本代码提交AC,用时39MS。
还有一种思路是,先对数组中两数之和Hash,然后枚举两个两数之和,转换为类似Two Sum的方法,时间复杂度为$O(n^2)$。