Algorithm Essentials CPP PDF
Algorithm Essentials CPP PDF
Algorithm Essentials CPP PDF
of Contents
介绍 1.1
线性表 1.2
数组 1.2.1
Remove Duplicates from Sorted Array 1.2.1.1
Remove Duplicates from Sorted Array II 1.2.1.2
Longest Consecutive Sequence 1.2.1.3
Two Sum 1.2.1.4
3Sum 1.2.1.5
3Sum Closest 1.2.1.6
4Sum 1.2.1.7
Remove Element 1.2.1.8
Move Zeroes 1.2.1.9
Next Permutation 1.2.1.10
Permutation Sequence 1.2.1.11
Valid Sudoku 1.2.1.12
Trapping Rain Water 1.2.1.13
Rotate Image 1.2.1.14
Plus One 1.2.1.15
Climbing Stairs 1.2.1.16
Set Matrix Zeroes 1.2.1.17
Gas Station 1.2.1.18
Candy 1.2.1.19
Majority Element 1.2.1.20
Rotate Array 1.2.1.21
Contains Duplicate 1.2.1.22
Contains Duplicate II 1.2.1.23
Contains Duplicate III 1.2.1.24
Product of Array Except Self 1.2.1.25
Game of Life 1.2.1.26
Increasing Triplet Subsequence 1.2.1.27
单链表 1.2.2
Reverse Linked List 1.2.2.1
Odd Even Linked List 1.2.2.2
Add Two Numbers 1.2.2.3
1
Reverse Linked List II 1.2.2.4
Partition List 1.2.2.5
Remove Duplicates from Sorted List 1.2.2.6
Remove Duplicates from Sorted List II 1.2.2.7
Rotate List 1.2.2.8
Remove Nth Node From End of List 1.2.2.9
Swap Nodes in Pairs 1.2.2.10
Reverse Nodes in k-Group 1.2.2.11
Copy List with Random Pointer 1.2.2.12
Linked List Cycle 1.2.2.13
Linked List Cycle II 1.2.2.14
Reorder List 1.2.2.15
LRU Cache 1.2.2.16
Palindrome Linked List 1.2.2.17
字符串 1.3
Valid Palindrome 1.3.1
Implement strStr() 1.3.2
String to Integer (atoi) 1.3.3
Add Binary 1.3.4
Longest Palindromic Substring 1.3.5
Regular Expression Matching 1.3.6
Wildcard Matching 1.3.7
Longest Common Prefix 1.3.8
Valid Number 1.3.9
Integer to Roman 1.3.10
Roman to Integer 1.3.11
Count and Say 1.3.12
Anagrams 1.3.13
Valid Anagram 1.3.14
Simplify Path 1.3.15
Length of Last Word 1.3.16
Isomorphic Strings 1.3.17
Word Pattern 1.3.18
栈和队列 1.4
栈 1.4.1
Min Stack 1.4.1.1
Valid Parentheses 1.4.1.2
2
Longest Valid Parentheses 1.4.1.3
Largest Rectangle in Histogram 1.4.1.4
Evaluate Reverse Polish Notation 1.4.1.5
Implement Stack using Queues 1.4.1.6
队列 1.4.2
Implement Queue using Stacks 1.4.2.1
二叉树 1.5
二叉树的遍历 1.5.1
Binary Tree Preorder Traversal 1.5.1.1
Binary Tree Inorder Traversal 1.5.1.2
Binary Tree Postorder Traversal 1.5.1.3
Binary Tree Level Order Traversal 1.5.1.4
Binary Tree Level Order Traversal II 1.5.1.5
Binary Tree Right Side View 1.5.1.6
Invert Binary Tree 1.5.1.7
Binary Search Tree Iterator 1.5.1.8
Binary Tree Zigzag Level Order Traversal 1.5.1.9
Recover Binary Search Tree 1.5.1.10
Same Tree 1.5.1.11
Symmetric Tree 1.5.1.12
Balanced Binary Tree 1.5.1.13
Flatten Binary Tree to Linked List 1.5.1.14
Populating Next Right Pointers in Each Node II 1.5.1.15
二叉树的构建 1.5.2
Construct Binary Tree from Preorder and Inorder Traversal 1.5.2.1
Construct Binary Tree from Inorder and Postorder Traversal 1.5.2.2
二叉查找树 1.5.3
Unique Binary Search Trees 1.5.3.1
Unique Binary Search Trees II 1.5.3.2
Validate Binary Search Tree 1.5.3.3
Convert Sorted Array to Binary Search Tree 1.5.3.4
Convert Sorted List to Binary Search Tree 1.5.3.5
LCA of BST 1.5.3.6
Kth Smallest Element in a BST 1.5.3.7
二叉树的递归 1.5.4
Minimum Depth of Binary Tree 1.5.4.1
Maximum Depth of Binary Tree 1.5.4.2
3
Path Sum 1.5.4.3
Path Sum II 1.5.4.4
Binary Tree Maximum Path Sum 1.5.4.5
Populating Next Right Pointers in Each Node 1.5.4.6
Sum Root to Leaf Numbers 1.5.4.7
LCA of Binary Tree 1.5.4.8
线段树 1.5.5
Range Sum Query - Mutable 1.5.5.1
排序 1.6
插入排序 1.6.1
Insertion Sort List 1.6.1.1
归并排序 1.6.2
Merge Two Sorted Arrays 1.6.2.1
Merge Two Sorted Lists 1.6.2.2
Merge k Sorted Lists 1.6.2.3
Sort List 1.6.2.4
快速排序 1.6.3
Sort Colors 1.6.3.1
Kth Largest Element in an Array 1.6.3.2
桶排序 1.6.4
First Missing Positive 1.6.4.1
计数排序 1.6.5
H-Index 1.6.5.1
基数排序 1.6.6
Maximum Gap 1.6.6.1
其他 1.6.7
Largest Number 1.6.7.1
小结 1.6.8
查找 1.7
Search for a Range 1.7.1
Search Insert Position 1.7.2
Search in Rotated Sorted Array 1.7.3
Search in Rotated Sorted Array II 1.7.4
Search a 2D Matrix 1.7.5
Search a 2D Matrix II 1.7.6
Find Minimum in Rotated Sorted Array 1.7.7
Find Minimum in Rotated Sorted Array II 1.7.8
4
Median of Two Sorted Arrays 1.7.9
H-Index II 1.7.10
暴力枚举法 1.8
Subsets 1.8.1
Subsets II 1.8.2
Permutations 1.8.3
Permutations II 1.8.4
Combinations 1.8.5
Letter Combinations of a Phone Number 1.8.6
广度优先搜索 1.9
Word Ladder 1.9.1
Word Ladder II 1.9.2
Surrounded Regions 1.9.3
总结 1.9.4
深度优先搜索 1.10
Additive Number 1.10.1
Palindrome Partitioning 1.10.2
Unique Paths 1.10.3
Unique Paths II 1.10.4
N-Queens 1.10.5
N-Queens II 1.10.6
Restore IP Addresses 1.10.7
Combination Sum 1.10.8
Combination Sum II 1.10.9
Combination Sum III 1.10.10
Generate Parentheses 1.10.11
Sudoku Solver 1.10.12
Word Search 1.10.13
总结 1.10.14
分治法 1.11
Pow(x,n) 1.11.1
Sqrt(x) 1.11.2
贪心法 1.12
Jump Game 1.12.1
Jump Game II 1.12.2
Best Time to Buy and Sell Stock 1.12.3
Best Time to Buy and Sell Stock II 1.12.4
5
Longest Substring Without Repeating Characters 1.12.5
Container With Most Water 1.12.6
Patching Array 1.12.7
动态规划 1.13
Triangle 1.13.1
Maximum Subarray 1.13.2
Maximum Product Subarray 1.13.3
Longest Increasing Subsequence 1.13.4
Palindrome Partitioning II 1.13.5
Maximal Rectangle 1.13.6
Best Time to Buy and Sell Stock III 1.13.7
Best Time to Buy and Sell Stock IV 1.13.8
Best Time to Buy and Sell Stock with Cooldown 1.13.9
Interleaving String 1.13.10
Scramble String 1.13.11
Minimum Path Sum 1.13.12
Edit Distance 1.13.13
Decode Ways 1.13.14
Distinct Subsequences 1.13.15
Word Break 1.13.16
Word Break II 1.13.17
Dungeon Game 1.13.18
House Robber 1.13.19
House Robber II 1.13.20
House Robber III 1.13.21
Range Sum Query - Immutable 1.13.22
Range Sum Query 2D - Immutable 1.13.23
图 1.14
Clone Graph 1.14.1
位操作 1.15
Reverse Bits 1.15.1
Repeated DNA Sequences 1.15.2
Number of 1 Bits 1.15.3
Gray Code 1.15.4
Single Number 1.15.5
Single Number II 1.15.6
Single Number III 1.15.7
6
Power of Two 1.15.8
Missing Number 1.15.9
Maximum Product of Word Lengths 1.15.10
Bitwise AND of Numbers Range 1.15.11
Power of Three 1.15.12
Rectangle Area 1.15.13
数论 1.16
Happy Number 1.16.1
Ugly Number 1.16.2
Ugly Number II 1.16.3
Super Ugly Number 1.16.4
Fraction to Recurring Decimal 1.16.5
Factorial Trailing Zeroes 1.16.6
Nim Game 1.16.7
模拟 1.17
Reverse Integer 1.17.1
Palindrome Number 1.17.2
Insert Interval 1.17.3
Merge Intervals 1.17.4
Minimum Window Substring 1.17.5
Multiply Strings 1.17.6
Substring with Concatenation of All Words 1.17.7
Pascal's Triangle 1.17.8
Pascal's Triangle II 1.17.9
Spiral Matrix 1.17.10
Spiral Matrix II 1.17.11
ZigZag Conversion 1.17.12
Divide Two Integers 1.17.13
Text Justification 1.17.14
Max Points on a Line 1.17.15
7
介绍
算法珠玑——一个最精简的题库
本书的目标读者是准备去硅谷找工作的码农,也适用于在国内找工作的码农,以及刚接触ACM算法竞赛的
新手。
市场上讲解算法的书已经汗牛充栋,为什么还要写这本书呢?主要原因是我对目前市场上的大部分算法书
都不太满意。 本书有如下特色:
1. 背后有强大的AlgoHub支持。
2. 每道题都有完整的代码。
市场上的大部分书,都会讲思路,但给出的代码都是片段,不是完整可编译的代码。本书每题都有完
整的代码,且每个代码经过千锤百炼,保证可读性的前提下尽可能简短,方面读者在面试中能快速写
出来。
3. 每道题都有多种解法。
本书的宗旨是,用尽可能少的题目,覆盖尽可能多的算法。本书中的的每道题都有多种解法,每种解
法不是简单的小改进,而是完全不同的思路,力求举一反三,让读者触类旁通。
4. 本书支持多种主流编程语言。
目前支持 Java, C++, C#, Python, Ruby, JavaScript, Swift, Scala, Clojure, 将来还会支持更多编程语
言。
在线阅读
https://www.gitbook.com/book/soulmachine/algorithm-essentials/
内容目录
介绍
线性表
数组
Remove Duplicates from Sorted Array
Remove Duplicates from Sorted Array II
Longest Consecutive Sequence
Two Sum
3Sum
3Sum Closest
4Sum
8
介绍
Remove Element
Move Zeroes
Next Permutation
Permutation Sequence
Valid Sudoku
Trapping Rain Water
Rotate Image
Plus One
Climbing Stairs
Set Matrix Zeroes
Gas Station
Candy
Majority Element
Rotate Array
Contains Duplicate
Contains Duplicate II
Contains Duplicate III
Product of Array Except Self
Game of Life
Increasing Triplet Subsequence
单链表
Reverse Linked List
Odd Even Linked List
Add Two Numbers
Reverse Linked List II
Partition List
Remove Duplicates from Sorted List
Remove Duplicates from Sorted List II
Rotate List
Remove Nth Node From End of List
Swap Nodes in Pairs
Reverse Nodes in k-Group
Copy List with Random Pointer
Linked List Cycle
Linked List Cycle II
Reorder List
LRU Cache
Palindrome Linked List
字符串
Valid Palindrome
Implement strStr()
String to Integer (atoi)
Add Binary
Longest Palindromic Substring
Regular Expression Matching
Wildcard Matching
9
介绍
10
介绍
11
介绍
Subsets
Subsets II
Permutations
Permutations II
Combinations
Letter Combinations of a Phone Number
广度优先搜索
Word Ladder
Word Ladder II
Surrounded Regions
总结
深度优先搜索
Additive Number
Palindrome Partitioning
Unique Paths
Unique Paths II
N-Queens
N-Queens II
Restore IP Addresses
Combination Sum
Combination Sum II
Combination Sum III
Generate Parentheses
Sudoku Solver
Word Search
总结
分治法
Pow(x,n)
Sqrt(x)
贪心法
Jump Game
Jump Game II
Best Time to Buy and Sell Stock
Best Time to Buy and Sell Stock II
Longest Substring Without Repeating Characters
Container With Most Water
Patching Array
动态规划
Triangle
Maximum Subarray
Maximum Product Subarray
Longest Increasing Subsequence
Palindrome Partitioning II
Maximal Rectangle
Best Time to Buy and Sell Stock III
Best Time to Buy and Sell Stock IV
12
介绍
13
介绍
Community
Github: https://www.github.com/soulmachine/algorithm-essentials
微博: @灵魂机器
小密圈:
14
介绍
License
Book License: CC BY-SA 3.0 License
15
线性表
这类题目考察线性表的操作,例如,数组,单链表,双向链表等。
16
数组
本节主要讲关于数组的各种算法题。
17
Remove Duplicates from Sorted Array
描述
Given a sorted array, remove the duplicates in place such that each element appear only once and return
the new length.
Do not allocate extra space for another array, you must do this in place with constant memory.
分析
无
代码
int index = 1;
for (int i = 1; i < nums.size(); i++) {
if (nums[i] != nums[index - 1])
nums[index++] = nums[i];
}
return index;
}
};
相关题目
Remove Duplicates from Sorted Array II
18
Remove Duplicates from Sorted Array II
描述
Follow up for "Remove Duplicates": What if duplicates are allowed at most twice?
For example, given sorted array A = [1,1,1,2,2,3] , your function should return length = 5 , and A is
now [1,1,2,2,3]
分析
加一个变量记录一下元素出现的次数即可。这题因为是已经排序的数组,所以一个变量即可解决。如果是
没有排序的数组,则需要引入一个hashmap来记录出现次数。
代码1
int index = 2;
for (int i = 2; i < nums.size(); i++){
if (nums[i] != nums[index - 2])
nums[index++] = nums[i];
}
return index;
}
};
代码2
下面是一个更简洁的版本。上面的代码略长,不过扩展性好一些,例如将 occur < 2 改为 occur < 3 ,
就变成了允许重复最多3次。
19
Remove Duplicates from Sorted Array II
nums[index++] = nums[i];
}
return index;
}
};
相关题目
Remove Duplicates from Sorted Array
20
Longest Consecutive Sequence
描述
Given an unsorted array of integers, find the length of the longest consecutive elements sequence.
For example, Given [100, 4, 200, 1, 3, 2] , The longest consecutive elements sequence is [1, 2,
3, 4] . Return its length: 4.
分析
如果允许O(n log n)的复杂度,那么可以先排序,可是本题要求 O(n) 。
用一个哈希表存储所有出现过的元素,对每个元素,以该元素为中心,往左右扩张,直到不连续为止,记
录下最长的长度。
代码
int longest = 0;
for (auto i : nums) {
int length = 1;
for (int j = i - 1; my_set.find(j) != my_set.end(); --j) {
my_set.erase(j);
++length;
}
for (int j = i + 1; my_set.find(j) != my_set.end(); ++j) {
my_set.erase(j);
++length;
}
longest = max(longest, length);
}
return longest;
}
};
21
Longest Consecutive Sequence
22
Two Sum
Two Sum
描述
Given an array of integers, find two numbers such that they add up to a specific target number.
The function twoSum should return indices of the two numbers such that they add up to the target, where
index1 must be less than index2. Please note that your returned answers (both index1 and index2) are
not zero-based.
You may assume that each input would have exactly one solution.
分析
方法1:暴力,复杂度O(n2 ),会超时
方法2:hash。用一个哈希表,存储每个数对应的下标,复杂度 O(n) .
代码
// Two Sum
// 方法2:hash。用一个哈希表,存储每个数对应的下标
// Time Complexity: O(n),Space Complexity: O(n)
class Solution {
public:
vector<int> twoSum(vector<int> &nums, int target) {
unordered_map<int, int> my_map;
vector<int> result;
for (int i = 0; i < nums.size(); i++) {
my_map[nums[i]] = i;
}
for (int i = 0; i < nums.size(); i++) {
auto iter = my_map.find(target-nums[i]);
if (iter != my_map.end() && iter->second > i) {
result.push_back(i + 1);
result.push_back(iter->second + 1);
break;
}
}
return result;
}
};
23
Two Sum
相关题目
3Sum
3Sum Closest
4Sum
24
3Sum
3Sum
描述
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0 ?
Find all unique triplets in the array which gives the sum of zero.
Note:
(-1, 0, 1)
(-1, -1, 2)
分析
先排序,然后左右夹逼,复杂度 O(n2 )。
代码
25
3Sum
// 3Sum
// 先排序,然后左右夹逼,注意跳过重复的数
// Time Complexity: O(n^2),Space Complexity: O(1)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
if (nums.size() < 3) return result;
sort(nums.begin(), nums.end());
const int target = 0;
相关题目
Two sum
3Sum Closest
4Sum
26
3Sum Closest
3Sum Closest
描述
Given an array S of n integers, find three integers in S such that the sum is closest to a given
number, target. Return the sum of the three integers. You may assume that each input would have exactly
one solution.
分析
先排序,然后左右夹逼,复杂度 O(n2 )。
代码
27
3Sum Closest
// 3Sum Closest
// 先排序,然后左右夹逼//
// Time Complexity: O(n^2), Space Complexity: O(1)
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int result = 0;
int min_gap = INT_MAX;
sort(nums.begin(), nums.end());
while (b < c) {
const int sum = *a + *b + *c;
const int gap = abs(sum - target);
return result;
}
};
相关题目
Two sum
3Sum
4Sum
28
4Sum
4Sum
描述
Given an array S of n integers, are there elements a, b, c , and d in S such that a + b + c +
d = target ? Find all unique quadruplets in the array which gives the sum of target.
Note:
(-1, 0, 0, 1)
(-2, -1, 1, 2)
(-2, 0, 0, 2)
分析
左右夹逼
29
4Sum
// 4Sum
// 先排序,然后左右夹逼
// Time Complexity: O(n^3),Space Complexity: O(1)
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
if (nums.size() < 4) return result;
sort(nums.begin(), nums.end());
HashMap 做缓存
30
4Sum
// 4Sum
// 用一个hashmap先缓存两个数的和
// Time Complexity: 平均O(n^2),最坏O(n^4),Space Complexity: O(n^2)
class Solution {
public:
vector<vector<int> > fourSum(vector<int> &nums, int target) {
vector<vector<int>> result;
if (nums.size() < 4) return result;
sort(nums.begin(), nums.end());
result.push_back( { nums[vec[k].first],
nums[vec[k].second], nums[c], nums[d] });
}
}
}
sort(result.begin(), result.end());
result.erase(unique(result.begin(), result.end()), result.end());
return result;
}
};
相关题目
Two sum
3Sum
3Sum Closest
31
Remove Element
Remove Element
描述
Given an array and a value, remove all instances of that value in place and return the new length.
The order of elements can be changed. It doesn't matter what you leave beyond the new length.
分析
无
代码
// Remove Element
// Time Complexity: O(n), Space Complexity: O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int target) {
int index = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != target) {
nums[index++] = nums[i];
}
}
return index;
}
};
相关题目
Move Zeroes
32
Move Zeroes
Move Zeroes
描述
Given an array nums , write a function to move all 0's to the end of it while maintaining the relative order
of the non-zero elements.
For example, given nums = [0, 1, 0, 3, 12] , after calling your function, nums should be [1, 3,
12, 0, 0] .
Note:
分析
这题跟 "Remove Element" 思路一模一样,只是最后要把后半截设置为0。
代码
相关题目
Remove Element
33
Next Permutation
Next Permutation
描述
Implement next permutation, which rearranges numbers into the lexicographically next greater
permutation of numbers.
If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in
ascending order).
Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the
right-hand column.
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
分析
算法过程如下图所示(来自http://fisherlei.blogspot.com/2012/12/leetcode-next-permutation.html)。
34
Next Permutation
Figure: 下一个排列算法流程
代码
35
Next Permutation
// Next Permutation
// Time Complexity: O(n), Space Complexity: O(1)
class Solution {
public:
void nextPermutation(vector<int> &nums) {
next_permutation(nums, 0, nums.size());
}
private:
bool next_permutation(vector<int> &nums, int begin, int end) {
// From right to left, find the first digit(partitionNumber)
// which violates the increase trend
int p = end - 2;
while (p > -1 && nums[p] >= nums[p + 1]) --p;
相关题目
Permutation Sequence
Permutations
Permutations II
Combinations
36
Permutation Sequence
Permutation Sequence
描述
The set [1,2,3,…,n] contains a total of n! unique permutations.
By listing and labeling all of the permutations in order, We get the following sequence (ie, for n = 3 ):
"123"
"132"
"213"
"231"
"312"
"321"
分析
首先可以想到一个简单直白的方法,即调用 k-1 次 next_permutation() ,从而得到第 k 个排列。这
个方法把前 k 个排列全部求出来了,比较浪费,时间复杂度是 O(kn) ,所以会超时。有没有办法直接求
第 k 个排列呢?有!
k2 = k%(n − 1)!
a2 = k2 /(n − 2)!
kn−1 = k n−2%2!
an−1 = kn−1/1!
an = 0
康托编码
37
Permutation Sequence
// Permutation Sequence
// 康托编码
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
string getPermutation(int n, int k) {
string s(n, '0');
string result;
for (int i = 0; i < n; ++i)
s[i] += i + 1;
// s 已排好序,是第一个排列
string kth_permutation(string &s, int k) {
const int n = s.size();
string result;
result.push_back(s[0]); // 最后一个
return result;
}
};
相关题目
Next Permutation
Permutations
Permutations II
Combinations
38
Valid Sudoku
Valid Sudoku
描述
Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules
http://sudoku.com.au/TheRules.aspx .
The Sudoku board could be partially filled, where empty cells are filled with the character '.' .
分析
细节实现题。
代码
39
Valid Sudoku
// Valid Sudoku
// 时间复杂度O(n^2),空间复杂度O(1)
class Solution {
public:
bool isValidSudoku(const vector<vector<char>>& board) {
bool used[9];
return true;
}
相关题目
Sudoku Solver
40
Trapping Rain Water
描述
Given n non-negative integers representing an elevation map where the width of each bar is 1,
compute how much water it is able to trap after raining.
分析
对于每个柱子,找到其左右两边最高的柱子,该柱子能容纳的面积就是 min(max_left, max_right) -
height 。所以,
1. 从左往右扫描一遍,对于每个柱子,求取左边最大值;
2. 从右往左扫描一遍,对于每个柱子,求最大右值;
3. 再扫描一遍,把每个柱子的面积并累加。
也可以,
1. 扫描一遍,找到最高的柱子,这个柱子将数组分为两半;
2. 处理左边一半;
3. 处理右边一半。
代码1
41
Trapping Rain Water
int sum = 0;
for (int i = 0; i < n; i++) {
int height = min(left_peak[i], right_peak[i]);
if (height > A[i]) {
sum += height - A[i];
}
}
delete[] left_peak;
delete[] right_peak;
return sum;
}
};
代码2
42
Trapping Rain Water
int water = 0;
for (int i = 0, left_peak = 0; i < peak_index; i++) {
if (A[i] > left_peak) left_peak = A[i];
else water += left_peak - A[i];
}
for (int i = n - 1, right_peak = 0; i > peak_index; i--) {
if (A[i] > right_peak) right_peak = A[i];
else water += right_peak - A[i];
}
return water;
}
};
相关题目
Container With Most Water
Largest Rectangle in Histogram
43
Rotate Image
Rotate Image
描述
You are given an n × n 2D matrix representing an image.
分析
首先想到,纯模拟,从外到内一圈一圈的转,但这个方法太慢。
如下图,首先沿着副对角线翻转一次,然后沿着水平中线翻转一次。
或者,首先沿着水平中线翻转一次,然后沿着主对角线翻转一次。
代码1
// Rotate Image
// 思路 1,时间复杂度O(n^2),空间复杂度O(1)
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
const int n = matrix.size();
代码2
44
Rotate Image
// Rotate Image
// 思路 2,时间复杂度O(n^2),空间复杂度O(1)
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
const int n = matrix.size();
45
Plus One
Plus One
描述
Given a number represented as an array of digits, plus one to the number.
分析
高精度加法。
代码
```cpp// Plus One // 时间复杂度O(n),空间复杂度O(1) class Solution { public: vector plusOne(vector
&digits) { add(digits, 1); return digits; } private: // 0 <= digit <= 9 void add(vector &digits, int digit) { int c =
digit; // carry, 进位
}; ```
46
Climbing Stairs
Climbing Stairs
描述
You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
分析
设 f(n) 表示爬 n 阶楼梯的不同方法数,为了爬到第 n 阶楼梯,有两个选择:
从第 n-1 阶前进1步;
从第 n-1 阶前进2步;
因此,有 f(n)=f(n-1)+f(n-2) 。
这是一个斐波那契数列。
方法1,递归,太慢;方法2,迭代。
n n
1 1 + √5 1 − √5
方法3,数学公式。斐波那契数列的通项公式为 an = [( ) −( ) ]。
√5 2 2
迭代
// Climbing Stairs
// 迭代,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int climbStairs(int n) {
int prev = 0;
int cur = 1;
for(int i = 1; i <= n ; ++i){
int tmp = cur;
cur += prev;
prev = tmp;
}
return cur;
}
};
数学公式
47
Climbing Stairs
// Climbing Stairs
// 数学公式,时间复杂度O(1),空间复杂度O(1)
class Solution {
public:
int climbStairs(int n) {
const double s = sqrt(5);
return floor((pow((1+s)/2, n+1) +
pow((1-s)/2, n+1))/s + 0.5);
}
};
相关题目
Decode Ways
48
Set Matrix Zeroes
描述
Given a m × n matrix, if an element is 0, set its entire row and column to 0. Do it in place.
A simple improvement uses O(m + n) space, but still not the best solution.
分析
O(m+n) 空间的方法很简单,设置两个bool数组,记录每行和每列是否存在0。
想要常数空间,可以复用第一行和第一列。
代码1
49
Set Matrix Zeroes
代码2
50
Set Matrix Zeroes
51
Gas Station
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, otherwise return -1.
分析
首先想到的是O(N 2 )的解法,对每个点进行模拟。
代码
// Gas Station
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
int total = 0;
int j = -1;
for (int i = 0, sum = 0; i < gas.size(); ++i) {
sum += gas[i] - cost[i];
total += gas[i] - cost[i];
if (sum < 0) {
j = i;
sum = 0;
}
}
return total >= 0 ? j + 1 : -1;
}
};
52
Candy
Candy
描述
There are N children standing in a line. Each child is assigned a rating value.
You are giving candies to these children subjected to the following requirements:
分析
无
迭代版
// Candy
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
int candy(vector<int> &ratings) {
const int n = ratings.size();
vector<int> increment(n);
// 左右各扫描一遍
for (int i = 1, inc = 1; i < n; i++) {
if (ratings[i] > ratings[i - 1])
increment[i] = max(inc++, increment[i]);
else
inc = 1;
}
递归版
53
Candy
// Candy
// 备忘录法,时间复杂度O(n),空间复杂度O(n)
// @author fancymouse (http://weibo.com/u/1928162822)
class Solution {
public:
int candy(const vector<int>& ratings) {
vector<int> f(ratings.size());
int sum = 0;
for (int i = 0; i < ratings.size(); ++i)
sum += solve(ratings, f, i);
return sum;
}
int solve(const vector<int>& ratings, vector<int>& f, int i) {
if (f[i] == 0) {
f[i] = 1;
if (i > 0 && ratings[i] > ratings[i - 1])
f[i] = max(f[i], solve(ratings, f, i - 1) + 1);
if (i < ratings.size() - 1 && ratings[i] > ratings[i + 1])
f[i] = max(f[i], solve(ratings, f, i + 1) + 1);
}
return f[i];
}
};
54
Majority Element
Majority Element
描述
Given an array of size n, find the majority element. The majority element is the element that appears more
than ⌊ n/2 ⌋ times.
You may assume that the array is non-empty and the majority element always exist in the array.
分析
这题最简单的解法,先把数组排序, O(nlogn) ,然后从头到尾扫描一遍,找出最长的连续子串。
解法1 排序
解法2 线性解法
55
Rotate Array
Rotate Array
描述
Rotate an array of n elements to the right by k steps.
Note: Try to come up as many solutions as you can, there are at least 3 different ways to solve this
problem.
分析
最简单的方法,开一个 k 长的数组,先把右边 k 个元素存入这个临时数组,然后把数组中的前 n-k 右
移 k 位,再把临时数组的 k 个元素存入到原始数组左边。时间复杂度 O(n) ,空间复杂度 O(k) 。
解法1 三轮reverse
56
Contains Duplicate
Contains Duplicate
描述
Given an array of integers, find if the array contains any duplicates. Your function should return true if any
value appears at least twice in the array, and it should return false if every element is distinct.
分析
方法1, 用一个 HashSet, 不断往里面塞元素,如果发现有重复,说明存在重复。时间复杂度 O(n) ,空间
复杂度 O(n) 。
解法1 哈希表
解法2 排序
57
Contains Duplicate II
Contains Duplicate II
描述
Given an array of integers and an integer k , find out whether there are two distinct indices i and j
in the array such that nums[i] = nums[j] and the difference between i and j is at most k .
分析
维护一个HashMap, key为整数,value为下标,将数组中的元素不断添加进这个HashMap, 碰到重复时,计
算二者的下标距离,如果距离小于或等于k,则返回true, 如果直到数组扫描完,距离都大于k,则返回
false。
代码
58
Contains Duplicate III
描述
Given an array of integers, find out whether there are two distinct indices i and j in the array such
that the difference between nums[i] and nums[j] is at most t and the difference between i and
j is at most k .
分析
这一题比 "Contains Duplicate II" 有多了个条件,难度陡然就增大了。
对于数组内的一个整数,如果能方便的求出大于它的最小整数和小于它的最大整数,那么我们就可以判断
差值是否大于 t 。能方便的求出最大下限和最小上限,最先想到的数据结构是二叉搜索树BST,因为左孩
子就是最大下限,右孩子就是最小上限。
可以用一个大小为k的滑动窗口,将窗口内的元素组织成一个BST,每次向前滑动一步,添加一个新元素,
同时删除一个最老的元素,如此不断向前滑动,不断更新BST。如果BST内部有两个元素差值大于 t ,则
返回true,如果直到扫描完数组,BST里都没有出现差值大于k的两个数,则返回false。
代码 滑动窗口+BST
59
Product of Array Except Self
描述
Given an array of n integers where n > 1 , nums , return an array output such that output[i] is
equal to the product of all the elements of nums except nums[i] .
Follow up:
Could you solve it with constant space complexity? (Note: The output array does not count as extra space
for the purpose of space complexity analysis.)
分析
我们以一个4个元素的数组为例, nums=[a1,a2,a3,a4] ,要想在 O(n) 的时间内输出结果,比较好的解
决方法是提前构造好两个数组:
代码1 O(n)空间
代码2 O(1)空间
60
Game of Life
Game of Life
描述
According to the Wikipedia's article: "The Game of Life, also known simply as Life, is a cellular
automaton devised by the British mathematician John Horton Conway in 1970."
Given a board with m by n cells, each cell has an initial state live (1) or dead (0). Each cell interacts with
its eight neighbors (horizontal, vertical, diagonal) using the following four rules (taken from the above
Wikipedia article):
1. Any live cell with fewer than two live neighbors dies, as if caused by under-population.
2. Any live cell with two or three live neighbors lives on to the next generation.
3. Any live cell with more than three live neighbors dies, as if by over-population..
4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
Write a function to compute the next state (after one update) of the board given its current state.
Follow up:
1. Could you solve it in-place? Remember that the board needs to be updated at the same time: You
cannot update some cells first and then use their updated values to update other cells.
2. In this question, we represent the board using a 2D array. In principle, the board is infinite, which
would cause problems when the active area encroaches the border of the array. How would you
address these problems?
分析
最简单的办法是新建一个矩阵保存下一轮局面。
因为题目给出的是一个int矩阵,大有空间可以利用。我们可以换一种方式进行编码,假设对于每个点,值
的含义为:
状态0:死细胞转为死细胞
状态1:活细胞转为活细胞
状态2:活细胞转为死细胞
状态3:死细胞转为活细胞
得到这样一个矩阵后,最后将所有状态对2取模,状态0和2变成死细胞,1和3变成活细胞,就是所求的下一
轮局面了。
代码
61
Increasing Triplet Subsequence
描述
Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the array.
More specifically, if there exists i , j , k such that arr[i] < arr[j] < arr[k] given 0 ≤ i < j
< k ≤ n-1 return true else return false .
Your function should run in O(n) time complexity and O(1) space complexity.
Examples:
分析
扫描一遍数组,用变量 x1 保存当前最小的值,变量 x2 保存当前第二小的值。如果右面能碰到一个数大
于 x2 ,说明必然存在一个递增的三元组。
代码
相关题目
Longest Increasing Subsequence
62
单链表
本节主要讲关于单链表的算法。
单链表节点的定义如下:
// 单链表节点
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) { }
};
63
Reverse Linked List
描述
Reverse a singly linked list.
分析
用三个指针 tail , p , q ,紧紧相邻,不断前进,每次将 p.next 指向 tail ,将 q.next 指向 p 。
解法1 迭代
解法2 递归
64
Odd Even Linked List
描述
Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we
are talking about the node number and not the value in the nodes.
You should try to do it in place. The program should run in O(1) space complexity and O(n) time
complexity.
Example:
Given 1->2->3->4->5->NULL ,
return 1->3->5->2->4->NULL .
Note:
1. The relative order inside both the even and odd groups should remain as it was in the input.
2. The first node is considered odd, the second node even and so on ...
分析
创建两个新的空链表,遍历原始链表,把奇数位置的节点添加到第一个小链表,把偶数位置的节点添加到
第二个小链表。
代码
65
Add Two Numbers
描述
You are given two linked lists representing two non-negative numbers. The digits are stored in reverse
order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
分析
跟 Add Binary 很类似
代码
相关题目
Add Binary
66
Add Two Numbers
67
Reverse Linked List II
描述
Reverse a linked list from position m to n . Do it in-place and in one-pass.
return 1->4->3->2->5->nullptr .
分析
这题非常繁琐,有很多边界检查,15分钟内做到bug free很有难度!
代码
prev = head2->next;
ListNode *cur = prev->next;
for (int i = m; i < n; ++i) {
prev->next = cur->next;
cur->next = head2->next;
head2->next = cur; // 头插法
cur = prev->next;
}
return dummy.next;
}
};
68
Partition List
Partition List
描述
Given a linked list and a value x , partition it such that all nodes less than x come before nodes
greater than or equal to x .
You should preserve the original relative order of the nodes in each of the two partitions.
分析
无
代码
// Partition List
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode left_dummy(-1); // 头结点
ListNode right_dummy(-1); // 头结点
left_cur->next = right_dummy.next;
right_cur->next = nullptr;
return left_dummy.next;
}
};
69
Remove Duplicates from Sorted List
描述
Given a sorted linked list, delete all duplicates such that each element appear only once.
For example,
分析
无
递归版
recur(&dummy, head);
return dummy.next;
}
private:
static void recur(ListNode *prev, ListNode *cur) {
if (cur == nullptr) return;
迭代版
70
Remove Duplicates from Sorted List
for (ListNode *prev = head, *cur = head->next; cur != nullptr; cur = prev->next
) {
if (prev->val == cur->val) {
prev->next = cur->next;
delete cur;
} else {
prev = cur;
}
}
return head;
}
};
相关题目
Remove Duplicates from Sorted List II
71
Remove Duplicates from Sorted List II
描述
Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers
from the original list.
For example,
分析
无
递归版
ListNode *p = head->next;
if (head->val == p->val) {
while (p != nullptr && head->val == p->val) {
ListNode *tmp = p;
p = p->next;
delete tmp;
}
delete head;
return deleteDuplicates(p);
} else {
head->next = deleteDuplicates(head->next);
return head;
}
}
};
迭代版
72
Remove Duplicates from Sorted List II
相关题目
Remove Duplicates from Sorted List
73
Rotate List
Rotate List
描述
Given a list, rotate the list to the right by k places, where k is non-negative.
分析
先遍历一遍,得出链表长度 len ,注意 k 可能大于 len ,因此令 k %= len 。将尾节点next指针指向首
节点,形成一个环,接着往后跑 len-k 步,从这里断开,就是要求的结果了。
代码
int len = 1;
ListNode* p = head;
while (p->next != nullptr) { // 求长度
len++;
p = p->next;
}
k = len - k % len;
74
Remove Nth Node From End of List
描述
Given a linked list, remove the n -th node from the end of list and return its head.
After removing the second node from the end, the linked list becomes 1->2->3->5 .
Note:
分析
设两个指针 p , q ,让 q 先走 n 步,然后 p 和 q 一起走,直到 q 走到尾节点,删除 p->next 即可。
代码
75
Swap Nodes in Pairs
描述
Given a linked list, swap every two adjacent nodes and return its head.
For example, Given 1->2->3->4 , you should return the list as 2->1->4->3 .
Your algorithm should use only constant space. You may not modify the values in the list, only nodes
itself can be changed.
分析
无
代码
下面这种写法更简洁,但题目规定了不准这样做。
76
Swap Nodes in Pairs
return head;
}
};
相关题目
Reverse Nodes in k-Group
77
Reverse Nodes in k-Group
描述
Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.
If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.
You may not alter the values in the nodes, only nodes itself may be changed.
分析
无
递归版
78
Reverse Nodes in k-Group
迭代版
79
Reverse Nodes in k-Group
return dummy.next;
}
相关题目
Swap Nodes in Pairs
80
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.
分析
无
代码
// 分拆两个单链表
RandomListNode dummy(-1);
for (RandomListNode* cur = head, *new_cur = &dummy;
cur != nullptr; ) {
new_cur->next = cur->next;
new_cur = new_cur->next;
cur->next = cur->next->next;
cur = cur->next;
}
return dummy.next;
}
};
81
Copy List with Random Pointer
82
Linked List Cycle
描述
Given a linked list, determine if it has a cycle in it.
分析
最容易想到的方法是,用一个哈希表 unordered_map<int, bool> visited ,记录每个元素是否被访问
过,一旦出现某个元素被重复访问,说明存在环。空间复杂度 O(n) ,时间复杂度 O(N) 。
代码
相关题目
Linked List Cycle II
83
Linked List Cycle II
描述
Given a linked list, return the node where the cycle begins. If there is no cycle, return null .
分析
当fast与slow相遇时,slow肯定没有遍历完链表,而fast已经在环内循环了 n 圈(1 ≤ n)。假设slow走
了 s 步,则fast走了 2s 步(fast步数还等于 s 加上在环上多转的 n 圈),设环长为 r ,则:
2s = s + nr
s = nr
x + a = nr = (n – 1)r +r = (n-1)r + L - x
x = (n-1)r + (L – x – a)
代码
84
Linked List Cycle II
相关题目
Linked List Cycle
85
Reorder List
Reorder List
描述
L 0 → Ln → L1 → Ln−1 → L 2 → Ln−2 → ⋯
分析
题目规定要in-place,也就是说只能使用 O(1) 的空间。
可以找到中间节点,断开,把后半截单链表reverse一下,再合并两个单链表。
代码
86
Reorder List
// Reorder List
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
void reorderList(ListNode *head) {
if (head == nullptr || head->next == nullptr) return;
slow = reverse(slow);
87
LRU Cache
LRU Cache
描述
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the
following operations: get and set.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise
return -1.
set(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.
分析
为了使查找、插入和删除都有较高的性能,这题的关键是要使用一个双向链表和一个HashMap,因为:
具体实现细节:
越靠近链表头部,表示节点上次访问距离现在时间最短,尾部的节点表示最近访问最少
访问节点时,如果节点存在,把该节点交换到链表头部,同时更新hash表中该节点的地址
插入节点时,如果cache的size达到了上限capacity,则删除尾部节点,同时要在hash表中删除对应的
项;新节点插入链表头部
代码
C++的 std::list 就是个双向链表,且它有个 splice() 方法, O(1) 时间,非常好用。
88
LRU Cache
// LRU Cache
// 时间复杂度O(logn),空间复杂度O(n)
class LRUCache{
private:
struct CacheNode {
int key;
int value;
CacheNode(int k, int v) :key(k), value(v){}
};
public:
LRUCache(int capacity) {
this->capacity = capacity;
}
// 把当前访问的节点移到链表头部,并且更新map中该节点的地址
cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
cacheMap[key] = cacheList.begin();
return cacheMap[key]->value;
}
89
Palindrome Linked List
描述
Given a singly linked list, determine if it is a palindrome.
Follow up:
分析
首先要寻找中点,原理是使用快慢指针,每次快指针走两步,慢指针走一步。同时还要用栈,每次慢指针
走一步,都把值存入栈中。等快指针走完时,链表的前半段都存入栈中了。最后慢指针继续往前走,每次
与栈顶元素进行比较。空间复杂度 O(n) 。
代码
90
字符串
字符串
本章主要讲字符串相关的算法。
91
Valid Palindrome
Valid Palindrome
描述
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring
cases.
For example,
"A man, a plan, a canal: Panama" is a palindrome. "race a car" is not a palindrome.
Note: Have you consider that the string might be empty? This is a good question to ask during an
interview.
For the purpose of this problem, we define empty string as valid palindrome.
分析
无
代码
// Valid Palindrome
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
bool isPalindrome(string s) {
transform(s.begin(), s.end(), s.begin(), ::tolower);
auto left = s.begin(), right = prev(s.end());
while (left < right) {
if (!::isalnum(*left)) ++left;
else if (!::isalnum(*right)) --right;
else if (*left != *right) return false;
else { left++, right--; }
}
return true;
}
};
相关题目
Palindrome Number
92
Implement strStr()
Implement strStr()
描述
Implement strStr().
Returns a pointer to the first occurrence of needle in haystack, or null if needle is not part of haystack.
分析
暴力算法的复杂度是 O(m*n) ,代码如下。更高效的的算法有KMP算法、Boyer-Mooer算法和Rabin-Karp
算法。面试中暴力算法足够了,一定要写得没有BUG。
暴力匹配
// Implement strStr()
// 暴力解法,时间复杂度O(N*M),空间复杂度O(1)
class Solution {
public:
int strStr(const string& haystack, const string& needle) {
if (needle.empty()) return 0;
KMP
// Implement strStr()
// KMP,时间复杂度O(N+M),空间复杂度O(M)
class Solution {
public:
int strStr(const string& haystack, const string& needle) {
return kmp(haystack.c_str(), needle.c_str());
}
private:
/*
93
Implement strStr()
* @brief 计算部分匹配表,即next数组.
*
* @param[in] pattern 模式串
* @param[out] next next数组
* @return 无
*/
static void compute_prefix(const char *pattern, int next[]) {
int i;
int j = -1;
const int m = strlen(pattern);
next[0] = j;
for (i = 1; i < m; i++) {
while (j > -1 && pattern[j + 1] != pattern[i]) j = next[j];
/*
* @brief KMP算法.
*
* @param[in] text 文本
* @param[in] pattern 模式串
* @return 成功则返回第一次匹配的位置,失败则返回-1
*/
static int kmp(const char *text, const char *pattern) {
int i;
int j = -1;
const int n = strlen(text);
const int m = strlen(pattern);
if (n == 0 && m == 0) return 0; /* "","" */
if (m == 0) return 0; /* "a","" */
int *next = (int*)malloc(sizeof(int) * m);
compute_prefix(pattern, next);
free(next);
return -1;
}
};
94
Implement strStr()
相关题目
String to Integer (atoi)
95
String to Integer (atoi)
描述
Implement atoi to convert a string to an integer.
Hint: Carefully consider all possible input cases. If you want a challenge, please do not see below and
ask yourself what are the possible input cases.
Notes: It is intended for this problem to be specified vaguely (ie, no given input specs). You are
responsible to gather all the input requirements up front.
The function first discards as many whitespace characters as necessary until the first non-whitespace
character is found. Then, starting from this character, takes an optional initial plus or minus sign followed
by as many numerical digits as possible, and interprets them as a numerical value.
The string can contain additional characters after those that form the integral number, which are ignored
and have no effect on the behavior of this function.
If the first sequence of non-whitespace characters in str is not a valid integral number, or if no such
sequence exists because either str is empty or it contains only whitespace characters, no conversion is
performed.
If no valid conversion could be performed, a zero value is returned. If the correct value is out of the range
of representable values, INT_MAX (2147483647) or INT_MIN (-2147483648) is returned.
分析
细节题。注意几个测试用例:
代码
96
String to Integer (atoi)
int i = 0;
while (str[i] == ' ' && i < n) i++;
if (str[i] == '+') {
i++;
} else if (str[i] == '-') {
sign = -1;
i++;
}
相关题目
Implement strStr()
97
Add Binary
Add Binary
描述
Given two binary strings, return their sum (also a binary string).
For example,
a = "11"
b = "1"
Return 100 .
分析
无
代码
// Add Binary
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
string addBinary(string a, string b) {
string result;
int i = a.length() - 1;
int j = b.length() - 1;
int carry = 0;
相关题目
Add Two Numbers
98
Longest Palindromic Substring
描述
Given a string S , find the longest palindromic substring in S . You may assume that the maximum
length of S is 1000, and there exists one unique longest palindromic substring.
分析
最长回文子串,非常经典的题。
思路一:暴力枚举,以每个元素为中间元素,同时从左右出发,复杂度 O(n^2) 。
f[i][j] = if (i == j) S[i]
if (S[i] == S[j] && f[i+1][j-1] == S[i+1][j-1]) S[i][j]
else max(f[i+1][j-1], f[i][j-1], f[i+1][j])
⎧true
⎪ ,i = j
f (i, j) = ⎨ S[i] = S[j] ,j = i + 1
⎪ S[i] = S[j] and f (i + 1, j − 1) , j > i + 1
⎩
备忘录法
99
Longest Palindromic Substring
class Solution {
public:
string longestPalindrome(string const& s) {
cache.clear();
return cachedLongestPalindrome(s, 0, s.length() - 1);
}
private:
unordered_map<pair<int, int>, string> cache;
动规
100
Longest Palindromic Substring
Manacher’s Algorithm
ret += "#$";
return ret;
}
string longestPalindrome(string s) {
string T = preProcess(s);
const int n = T.length();
// 以T[i]为中心,向左/右扩张的长度,不包含T[i]自己,
101
Longest Palindromic Substring
// 因此 P[i]是源字符串中回文串的长度
int P[n];
int C = 0, R = 0;
102
Regular Expression Matching
描述
Implement regular expression matching with support for '.' and '*' .
'.' Matches any single character. '*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true
分析
这是一道很有挑战的题。
递归版
103
Regular Expression Matching
相关题目
Wildcard Matching
104
Wildcard Matching
Wildcard Matching
描述
Implement wildcard pattern matching with support for '?' and '*' .
'?' Matches any single character. '*' Matches any sequence of characters (including the empty
sequence).
The matching should cover the entire input string (not partial).
Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false
分析
跟上一题很类似。
主要是 '*' 的匹配问题。 p 每遇到一个 '*' ,就保留住当前 '*' 的坐标和 s 的坐标,然后 s 从前往
后扫描,如果不成功,则 s++ ,重新扫描。
递归版
105
Wildcard Matching
// Wildcard Matching
// 递归版,会超时,用于帮助理解题意
// 时间复杂度O(n!*m!),空间复杂度O(n)
class Solution {
public:
bool isMatch(const string& s, const string& p) {
return isMatch(s.c_str(), p.c_str());
}
private:
bool isMatch(const char *s, const char *p) {
if (*p == '\0' || *s == '\0') return *p == *s;
else if (*p == '*') {
while (*p == '*') ++p; //skip continuous '*'
if (*p == '\0') return true;
while (*s != '\0' && !isMatch(s, p)) ++s;
return *s != '\0';
}
else if (*p == *s || *p == '?') return isMatch(++s, ++p);
else return false;
}
};
迭代版
106
Wildcard Matching
// Wildcard Matching
// 迭代版,时间复杂度O(n*m),空间复杂度O(1)
class Solution {
public:
bool isMatch(const string& s, const string& p) {
return isMatch(s.c_str(), p.c_str());
}
private:
bool isMatch(const char *s, const char *p) {
bool star = false;
const char *str, *ptr;
for (str = s, ptr = p; *str != '\0'; str++, ptr++) {
switch (*ptr) {
case '?':
break;
case '*':
star = true;
s = str, p = ptr;
while (*p == '*') p++; //skip continuous '*'
if (*p == '\0') return true;
str = s - 1;
ptr = p - 1;
break;
default:
if (*str != *ptr) {
// 如果前面没有'*',则匹配不成功
if (!star) return false;
s++;
str = s - 1;
ptr = p - 1;
}
}
}
while (*ptr == '*') ptr++;
return (*ptr == '\0');
}
};
相关题目
Regular Expression Matching
107
Longest Common Prefix
描述
Write a function to find the longest common prefix string amongst an array of strings.
分析
从位置0开始,对每一个位置比较所有字符串,直到遇到一个不匹配。
纵向扫描
横向扫描
108
Longest Common Prefix
109
Valid Number
Valid Number
描述
Validate if a given string is numeric.
Some examples:
Note: It is intended for the problem statement to be ambiguous. You should gather all requirements up
front before implementing one.
分析
细节实现题。
有限自动机
// Valid Number
// @author 龚陆安 (http://weibo.com/luangong)
// finite automata,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
bool isNumber(const string& s) {
enum InputType {
INVALID, // 0
SPACE, // 1
SIGN, // 2
DIGIT, // 3
DOT, // 4
EXPONENT, // 5
NUM_INPUTS // 6
};
const int transitionTable[][NUM_INPUTS] = {
-1, 0, 3, 1, 2, -1, // next states for state 0
-1, 8, -1, 1, 4, 5, // next states for state 1
-1, -1, -1, 4, -1, -1, // next states for state 2
-1, -1, -1, 1, 2, -1, // next states for state 3
-1, 8, -1, 4, -1, 5, // next states for state 4
-1, -1, 6, 7, -1, -1, // next states for state 5
-1, -1, -1, 7, -1, -1, // next states for state 6
-1, 8, -1, 7, -1, -1, // next states for state 7
110
Valid Number
int state = 0;
for (auto ch : s) {
InputType inputType = INVALID;
if (isspace(ch))
inputType = SPACE;
else if (ch == '+' || ch == '-')
inputType = SIGN;
else if (isdigit(ch))
inputType = DIGIT;
else if (ch == '.')
inputType = DOT;
else if (ch == 'e' || ch == 'E')
inputType = EXPONENT;
// Invalid input
if (state == -1) return false;
}
// If the current state belongs to one of the accepting (final) states,
// then the number is valid
return state == 1 || state == 4 || state == 7 || state == 8;
}
};
111
Integer to Roman
Integer to Roman
描述
Given an integer, convert it to a roman numeral.
分析
无
代码
// Integer to Roman
// 时间复杂度O(num),空间复杂度O(1)
class Solution {
public:
string intToRoman(int num) {
const int radix[] = {1000, 900, 500, 400, 100, 90,
50, 40, 10, 9, 5, 4, 1};
const string symbol[] = {"M", "CM", "D", "CD", "C", "XC",
"L", "XL", "X", "IX", "V", "IV", "I"};
string roman;
for (size_t i = 0; num > 0; ++i) {
int count = num / radix[i];
num %= radix[i];
for (; count > 0; --count) roman += symbol[i];
}
return roman;
}
};
相关题目
Roman to Integer
112
Roman to Integer
Roman to Integer
描述
Given a roman numeral, convert it to an integer.
分析
从前往后扫描,用一个临时变量记录分段数字。
如果当前比前一个大,说明这一段的值应该是当前这个值减去上一个值。比如 IV = 5 – 1 ;否则,将当
前值加入到结果中,然后开始下一段记录。比如 VI = 5 + 1, II=1+1
代码
// Roman to Integer
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
inline int map(const char c) {
switch (c) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return 0;
}
}
相关题目
113
Roman to Integer
Integer to Roman
114
Count and Say
描述
The count-and-say sequence is the sequence of integers beginning as follows:
分析
模拟。
代码
115
Count and Say
while (--n)
s = getNext(s);
return s;
}
return ss.str();
}
};
116
Anagrams
Anagrams
描述
Given an array of strings, return all groups of strings that are anagrams.
分析
Anagram(回文构词法)是指打乱字母顺序从而得到新的单词,比如 "dormitory" 打乱字母顺序会变成
"dirty room" , "tea" 会变成 "eat" 。
回文构词法有一个特点:单词里的字母的种类和数目没有改变,只是改变了字母的排列顺序。因此,将几
个单词按照字母顺序排序后,若它们相等,则它们属于同一组 anagrams 。
代码
117
Valid Anagram
Valid Anagram
描述
Given two strings s and t , write a function to determine if t is an anagram of s .
For example,
Note:
分析
首先能够想到的是,为 s 和 t 分别建立一个HashMap,统计每个字母出现的次数,比较两个HashMap是
否相等。时间复杂度 O(n) ,空间复杂度 O(n) , n 为字符串长度。
代码
118
Simplify Path
Simplify Path
描述
Given an absolute path for a file (Unix-style), simplify it.
For example,
Corner Cases:
Another corner case is the path might contain multiple slashes '/' together, such as
"/home//foo/" .
In this case, you should ignore redundant slashes and return "/home/foo" .
分析
很有实际价值的题目。
代码
119
Simplify Path
// Simplify Path
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
string simplifyPath(const string& path) {
vector<string> dirs; // 当做栈
i = j;
}
stringstream out;
if (dirs.empty()) {
out << "/";
} else {
for (auto dir : dirs)
out << '/' << dir;
}
return out.str();
}
};
120
Length of Last Word
描述
Given a string s consists of upper/lower-case alphabets and empty space characters ' ' , return the
length of last word in the string.
分析
模拟。先从右到左找到第一个字母,然后从右到左找到第一个非字母,二者的距离就是最后一个word的长
度。
代码
121
Isomorphic Strings
Isomorphic Strings
描述
Given two strings s and t , determine if they are isomorphic.
All occurrences of a character must be replaced with another character while preserving the order of
characters. No two characters may map to the same character but a character may map to itself.
For example,
Note:
分析
用两个HashMap维护字符的映射关系,时间复杂度 O(n) ,空间复杂度 O(n) 。
代码
相关题目
Word Pattern
122
Word Pattern
Word Pattern
描述
Given a pattern and a string str , find if str follows the same pattern.
Here follow means a full match, such that there is a bijection between a letter in pattern and a non-
empty word in str .
Examples:
1. pattern = "abba" , str = "dog cat cat dog" should return true.
2. pattern = "abba" , str = "dog cat cat fish" should return false.
3. pattern = "aaaa" , str = "dog cat cat dog" should return false.
4. pattern = "abba" , str = "dog dog dog dog" should return false.
Notes:
You may assume pattern contains only lowercase letters, and str contains lowercase letters
separated by a single space.
分析
本题跟 "Isomorphic Strings" 很类似,用两个HashMap, 记录从字符到字符串和字符串到字符的映射。
代码
相关题目
Isomorphic Strings
123
栈和队列
本章主要讲栈和队列相关的算法。
124
栈
本节主要讲栈相关的算法。
125
Min Stack
Min Stack
描述
Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.
分析
用两个栈,一个是真实的栈,另一个作为辅助栈,辅助栈每次 push 时,会把新元素跟当前栈顶元素进行比
较,存入二者中较小的那个。
代码
126
Valid Parentheses
Valid Parentheses
描述
Given a string containing just the characters '(' , ')' , '{' , '}' , '[' and ']' , determine if
the input string is valid.
The brackets must close in the correct order, "()" and "()[]{}" are all valid but "(]" and "
([)]" are not.
分析
无
代码
// Valid Parentheses
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
bool isValid (string const& s) {
string left = "([{";
string right = ")]}";
stack<char> stk;
for (auto c : s) {
if (left.find(c) != string::npos) {
stk.push (c);
} else {
if (stk.empty () || stk.top () != left[right.find (c)])
return false;
else
stk.pop ();
}
}
return stk.empty();
}
};
相关题目
Generate Parentheses
Longest Valid Parentheses
127
Longest Valid Parentheses
描述
Given a string containing just the characters '(' and ')' , find the length of the longest valid (well-
formed) parentheses substring.
For "(()" , the longest valid parentheses substring is "()" , which has length = 2.
Another example is ")()())" , where the longest valid parentheses substring is "()()" , which has
length = 4.
分析
无
使用栈
128
Longest Valid Parentheses
depth = 0;
start = s.size();
for (int i = s.size() - 1; i >= 0; --i) {
if (s[i] == ')') {
++depth;
} else {
--depth;
if (depth < 0) {
start = i;
depth = 0;
} else if (depth == 0) {
result = max(result, start - i);
}
}
}
return result;
}
};
两遍扫描
129
Longest Valid Parentheses
depth = 0;
start = s.size();
for (int i = s.size() - 1; i >= 0; --i) {
if (s[i] == ')') {
++depth;
} else {
--depth;
if (depth < 0) {
start = i;
depth = 0;
} else if (depth == 0) {
result = max(result, start - i);
}
}
}
return result;
}
};
相关题目
Valid Parentheses
Generate Parentheses
130
Largest Rectangle in Histogram
描述
Given n non-negative integers representing the histogram's bar height where the width of each bar is 1,
find the area of largest rectangle in the histogram.
Figure: Above is a histogram where width of each bar is 1, given height = `[2,1,5,6,2,3]`.
Figure: The largest rectangle is shown in the shaded area, which has area = 10 unit.
分析
简单的,类似于 Container With Most Water,对每个柱子,左右扩展,直到碰到比自己矮的,计算这个矩
形的面积,用一个变量记录最大的面积,复杂度 O(n^2) ,会超时。
131
Largest Rectangle in Histogram
这就意味着,可以维护一个递增的栈,每次比较栈顶与当前元素。如果当前元素大于栈顶元素,则入栈,
否则合并现有栈,直至栈顶元素小于当前元素。结尾时入栈元素0,重复合并一次。
代码
相关题目
Trapping Rain Water
Container With Most Water
132
Evaluate Reverse Polish Notation
描述
Evaluate the value of an arithmetic expression in Reverse Polish Notation.
Some examples:
分析
逆波兰表达式是典型的递归结构,所以可以用递归来求解,也可以用栈来求解。
递归版
133
Evaluate Reverse Polish Notation
迭代版
134
Implement Stack using Queues
描述
Implement the following operations of a stack using queues.
Notes:
You must use only standard operations of a queue -- which means only push to back, peek/pop from
front, size, and is empty operations are valid.
Depending on your language, queue may not be supported natively. You may simulate a queue by
using a list or deque (double-ended queue), as long as you use only standard operations of a queue.
You may assume that all operations are valid (for example, no pop or top operations will be called on
an empty stack).
分析
可以用两个队列, q 和 tmp , q 存放元素, tmp 用来作中转。
代码
相关题目
Implement Queue using Stacks
135
队列
本节主要讲队列相关的算法。
136
Implement Queue using Stacks
描述
Implement the following operations of a queue using stacks.
Notes:
You must use only standard operations of a stack -- which means only push to top, peek/pop from
top, size, and is empty operations are valid.
Depending on your language, stack may not be supported natively. You may simulate a stack by
using a list or deque (double-ended queue), as long as you use only standard operations of a stack.
You may assume that all operations are valid (for example, no pop or peek operations will be called
on an empty queue).
分析
可以用两个栈, s 和 tmp , s 存放元素, tmp 用来作中转。
代码
相关题目
Implement Stack using Queues
137
二叉树
本章主要讲树相关的算法。
LeetCode 上二叉树的节点定义如下:
// 树的节点
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) { }
};
138
二叉树的遍历
二叉树的遍历
树的遍历有两类:深度优先遍历和宽度优先遍历。深度优先遍历又可分为两种:先根(次序)遍历和后根
(次序)遍历。
树的先根遍历是:先访问树的根结点,然后依次先根遍历根的各棵子树。树的先跟遍历的结果与对应二叉
树(孩子兄弟表示法)的先序遍历的结果相同。
树的后根遍历是:先依次后根遍历树根的各棵子树,然后访问根结点。树的后跟遍历的结果与对应二叉树
的中序遍历的结果相同。
二叉树的先根遍历有:先序遍历(root->left->right),root->right->left;后根遍历有:后序遍历(left->right-
>root),right->left->root;二叉树还有个一般的树没有的遍历次序,中序遍历(left->root->right)。
139
Binary Tree Preorder Traversal
描述
Given a binary tree, return the preorder traversal of its nodes' values.
1
\
2
/
3
return [1,2,3] .
分析
用栈或者Morris遍历。
while (!s.empty()) {
const TreeNode *p = s.top();
s.pop();
result.push_back(p->val);
Morris先序遍历
140
Binary Tree Preorder Traversal
相关题目
Binary Tree Inorder Traversal
Binary Tree Postorder Traversal
Recover Binary Search Tree
141
Binary Tree Inorder Traversal
描述
Given a binary tree, return the inorder traversal of its nodes' values.
For example:
1
\
2
/
3
return [1,3,2] .
分析
用栈或者Morris遍历。
142
Binary Tree Inorder Traversal
Morris中序遍历
143
Binary Tree Inorder Traversal
相关题目
Binary Tree Preorder Traversal
Binary Tree Postorder Traversal
Recover Binary Search Tree
144
Binary Tree Postorder Traversal
描述
Given a binary tree, return the postorder traversal of its nodes' values.
1
\
2
/
3
return [3,2,1] .
分析
用栈或者Morris遍历。
145
Binary Tree Postorder Traversal
do {
while (p != nullptr) { /* 往左下走*/
s.push(p);
p = p->left;
}
q = nullptr;
while (!s.empty()) {
p = s.top();
s.pop();
/* 右孩子不存在或已被访问,访问之*/
if (p->right == q) {
result.push_back(p->val);
q = p; /* 保存刚访问过的结点*/
} else {
/* 当前结点不能访问,需第二次进栈*/
s.push(p);
/* 先处理右子树*/
p = p->right;
break;
}
}
} while (!s.empty());
return result;
}
};
Morris后序遍历
146
Binary Tree Postorder Traversal
dummy.left = root;
cur = &dummy;
while (cur != nullptr) {
if (cur->left == nullptr) {
prev = cur; /* 必须要有 */
cur = cur->right;
} else {
TreeNode *node = cur->left;
while (node->right != nullptr && node->right != cur)
node = node->right;
while (x != to) {
z = y->right;
y->right = x;
x = y;
y = z;
}
}
// 访问逆转后的路径上的所有结点
static void visit_reverse(TreeNode* from, TreeNode *to,
std::function< void(const TreeNode*) >& visit) {
TreeNode *p = to;
reverse(from, to);
while (true) {
visit(p);
if (p == from)
break;
p = p->right;
}
147
Binary Tree Postorder Traversal
reverse(to, from);
}
};
相关题目
Binary Tree Preorder Traversal
Binary Tree Inorder Traversal
Recover Binary Search Tree
148
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).
3
/ \
9 20
/ \
15 7
[
[3],
[9,20],
[15,7]
]
分析
无
递归版
149
Binary Tree Level Order Traversal
result[level-1].push_back(root->val);
traverse(root->left, level+1, result);
traverse(root->right, level+1, result);
}
};
迭代版
150
Binary Tree Level Order Traversal
if(root == nullptr) {
return result;
} else {
current.push(root);
}
while (!current.empty()) {
vector<int> level; // elments in one level
while (!current.empty()) {
TreeNode* node = current.front();
current.pop();
level.push_back(node->val);
if (node->left != nullptr) next.push(node->left);
if (node->right != nullptr) next.push(node->right);
}
result.push_back(level);
swap(next, current);
}
return result;
}
};
相关题目
Binary Tree Level Order Traversal II
Binary Tree Zigzag Level Order Traversal
151
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).
3
/ \
9 20
/ \
15 7
[
[15,7]
[9,20],
[3],
]
分析
在上一题 Binary Tree Level Order Traversal 的基础上, reverse() 一下即可。
递归版
152
Binary Tree Level Order Traversal II
result[level-1].push_back(root->val);
traverse(root->left, level+1, result);
traverse(root->right, level+1, result);
}
};
迭代版
153
Binary Tree Level Order Traversal II
current.push(root);
while (!current.empty()) {
while (!current.empty()) {
TreeNode* node = current.front();
current.pop();
level.push_back(node->val);
if (node->left != nullptr) next.push(node->left);
if (node->right != nullptr) next.push(node->right);
}
result.push_back(level);
level.clear();
swap(next, current);
}
reverse(result.begin(), result.end()); // 比上一题多此一行
return result;
}
};
相关题目
Binary Tree Level Order Traversal
Binary Tree Zigzag Level Order Traversal
154
Binary Tree Right Side View
描述
Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you
can see ordered from top to bottom.
1 <---
/ \
2 3 <---
\ \
5 4 <---
分析
层次遍历。
代码
155
Invert Binary Tree
描述
Invert a binary tree.
4
/ \
2 7
/ \ / \
1 3 6 9
to
4
/ \
7 2
/ \ / \
9 6 3 1
分析
这题是大名鼎鼎的 Homebrew 的作者 Max Howell 在 Twitter 上发牢骚的那道题。原始 Tweet 地
址:https://twitter.com/mxcl/status/608682016205344768
这题最简单的办法,是层次遍历,每次交换左右子树。
但是,这题也可以用递归解决,代码非常短。
解法1 层次遍历
解法2 递归
156
Binary Search Tree Iterator
描述
Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of
a BST.
Calling next() will return the next smallest number in the BST.
Note: next() and hasNext() should run in average O(1) time and uses O(h) memory, where h is
the height of the tree.
分析
考察非递归的中序遍历。这道题本质上是写一个二叉树的中序遍历的迭代器。内部设置一个栈,初始化的
时候,存储从根节点到最左叶子节点的路径。在遍历的过程中,每次从栈中弹出一个元素,作为当前的返
回结果,同时探测一下当前节点是否存在右孩子,如果有,则进入右孩子,并把从该右孩子到最左叶子节
点的所有节点入栈。
代码
157
Binary Tree Zigzag Level Order Traversal
描述
Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then
right to left for the next level and alternate between).
For example:
3
/ \
9 20
/ \
15 7
[
[3],
[20,9],
[15,7]
]
分析
广度优先遍历,用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下。
递归版
158
Binary Tree Zigzag Level Order Traversal
if (left_to_right)
result[level-1].push_back(root->val);
else
result[level-1].insert(result[level-1].begin(), root->val);
迭代版
159
Binary Tree Zigzag Level Order Traversal
while (!current.empty()) {
vector level; // elments in one level
while (!current.empty()) {
TreeNode* node = current.front();
current.pop();
level.push_back(node->val);
if (node->left != nullptr) next.push(node->left);
if (node->right != nullptr) next.push(node->right);
}
if (!left_to_right) reverse(level.begin(), level.end());
result.push_back(level);
left_to_right = !left_to_right;
swap(next, current);
}
return result;
}
};
相关题目
Binary Tree Level Order Traversal
Binary Tree Level Order Traversal II
160
Recover Binary Search Tree
描述
Two elements of a binary search tree (BST) are swapped by mistake.
Note: A solution using O(n) space is pretty straight forward. Could you devise a constant space
solution?
分析
O(logn) 空间的解法是,中序递归遍历,用两个指针存放在遍历过程中碰到的两处逆向的位置。
中序遍历,递归方式
161
Recover Binary Search Tree
Morris中序遍历
if (node->right == nullptr) {
node->right = cur;
//prev = cur; 不能有这句!因为cur还没有被访问
cur = cur->left;
} else {
detect(broken, prev, cur);
node->right = nullptr;
prev = cur;
cur = cur->right;
}
}
}
swap(broken.first->val, broken.second->val);
}
相关题目
162
Recover Binary Search Tree
163
Same Tree
Same Tree
描述
Given two binary trees, write a function to check if they are equal or not.
Two binary trees are considered equal if they are structurally identical and the nodes have the same
value.
分析
无
递归版
// Same Tree
// 递归版,时间复杂度O(n),空间复杂度O(logn)
class Solution {
public:
bool isSameTree(TreeNode *p, TreeNode *q) {
if (!p && !q) return true; // 终止条件
if (!p || !q) return false; // 剪枝
return p->val == q->val // 三方合并
&& isSameTree(p->left, q->left)
&& isSameTree(p->right, q->right);
}
};
迭代版
164
Same Tree
// Same Tree
// 迭代版,时间复杂度O(n),空间复杂度O(logn)
class Solution {
public:
bool isSameTree(TreeNode *p, TreeNode *q) {
stack<TreeNode*> s;
s.push(p);
s.push(q);
while(!s.empty()) {
p = s.top(); s.pop();
q = s.top(); s.pop();
s.push(p->left);
s.push(q->left);
s.push(p->right);
s.push(q->right);
}
return true;
}
};
相关题目
Symmetric Tree
165
Symmetric Tree
Symmetric Tree
描述
Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).
1
/ \
2 2
/ \ / \
3 4 4 3
1
/ \
2 2
\ \
3 3
Note: Bonus points if you could solve it both recursively and iteratively.
分析
无
递归版
// Symmetric Tree
// 递归版,时间复杂度O(n),空间复杂度O(logn)
class Solution {
public:
bool isSymmetric(TreeNode *root) {
if (root == nullptr) return true;
return isSymmetric(root->left, root->right);
}
bool isSymmetric(TreeNode *p, TreeNode *q) {
if (p == nullptr && q == nullptr) return true; // 终止条件
if (p == nullptr || q == nullptr) return false; // 终止条件
return p->val == q->val // 三方合并
&& isSymmetric(p->left, q->right)
&& isSymmetric(p->right, q->left);
}
};
166
Symmetric Tree
迭代版
// Symmetric Tree
// 迭代版,时间复杂度O(n),空间复杂度O(logn)
class Solution {
public:
bool isSymmetric (TreeNode* root) {
if (!root) return true;
stack<TreeNode*> s;
s.push(root->left);
s.push(root->right);
s.push(p->left);
s.push(q->right);
s.push(p->right);
s.push(q->left);
}
return true;
}
};
相关题目
Same Tree
167
Balanced Binary Tree
描述
Given a binary tree, determine if it is height-balanced.
For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two
subtrees of every node never differ by more than 1.
分析
无
代码
/**
* Returns the height of `root` if `root` is a balanced tree,
* otherwise, returns `-1`.
*/
int balancedHeight (TreeNode* root) {
if (root == nullptr) return 0; // 终止条件
168
Flatten Binary Tree to Linked List
描述
Given a binary tree, flatten it to a linked list in-place.
1
/ \
2 5
/ \ \
3 4 6
1
\
2
\
3
\
4
\
5
\
6
分析
无
递归版1
169
Flatten Binary Tree to Linked List
flatten(root->left);
flatten(root->right);
// 三方合并,将左子树所形成的链表插入到root和root->right之间
TreeNode *p = root->left;
while(p->right) p = p->right; //寻找左链表最后一个节点
p->right = root->right;
root->right = root->left;
root->left = nullptr;
}
};
递归版2
170
Populating Next Right Pointers in Each Node II
描述
Follow up for problem "Populating Next Right Pointers in Each Node".
What if the given tree could be any binary tree? Would your previous solution still work?
1
/ \
2 3
/ \ \
4 5 7
1 -> NULL
/ \
2 -> 3 -> NULL
/ \ \
4-> 5 -> 7 -> NULL
分析
要处理一个节点,可能需要最右边的兄弟节点,首先想到用广搜。但广搜不是常数空间的,本题要求常数
空间。
递归版
171
Populating Next Right Pointers in Each Node II
TreeLinkNode dummy(-1);
for (TreeLinkNode *curr = root, *prev = &dummy;
curr; curr = curr->next) {
if (curr->left != nullptr){
prev->next = curr->left;
prev = prev->next;
}
if (curr->right != nullptr){
prev->next = curr->right;
prev = prev->next;
}
}
connect(dummy.next);
}
};
迭代版
if (root->left) {
if (prev) prev->next = root->left;
prev = root->left;
}
if (root->right) {
if (prev) prev->next = root->right;
prev = root->right;
}
}
root = next; // turn to next level
}
}
};
172
Populating Next Right Pointers in Each Node II
相关题目
Populating Next Right Pointers in Each Node
173
二叉树的构建
本节主要讲二叉树的构建。
174
Construct Binary Tree from Preorder and Inorder Traversal
描述
Given preorder and inorder traversal of a tree, construct the binary tree.
Note: You may assume that duplicates do not exist in the tree.
分析
无
代码
template
TreeNode* buildTree(InputIterator pre_first, InputIterator pre_last,
InputIterator in_first, InputIterator in_last) {
if (pre_first == pre_last) return nullptr;
if (in_first == in_last) return nullptr;
return root;
}
};
相关题目
Construct Binary Tree from Inorder and Postorder Traversal
175
Construct Binary Tree from Preorder and Inorder Traversal
176
Construct Binary Tree from Inorder and Postorder Traversal
描述
Given inorder and postorder traversal of a tree, construct the binary tree.
Note: You may assume that duplicates do not exist in the tree.
分析
无
代码
template
TreeNode* buildTree(BidiIt in_first, BidiIt in_last,
BidiIt post_first, BidiIt post_last) {
if (in_first ==in_last) return nullptr;
if (post_first == post_last) return nullptr;
return root;
}
};
相关题目
Construct Binary Tree from Preorder and Inorder Traversal
177
Construct Binary Tree from Inorder and Postorder Traversal
178
二叉查找树
本节主要讲二叉查找树。
179
Unique Binary Search Trees
描述
Given n , how many structurally unique BST's (binary search trees) that store values 1...n ?
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
分析
如果把上例的顺序改一下,就可以看出规律了。
1 1 2 3 3
\ \ / \ / /
3 2 1 3 2 1
/ \ / \
2 3 1 2
比如,以1为根的树的个数,等于左子树的个数乘以右子树的个数,左子树是0个元素的树,右子树是2个元
素的树。以2为根的树的个数,等于左子树的个数乘以右子树的个数,左子树是1个元素的树,右子树也是1
个元素的树。依此类推。
如果数组为空,毫无疑问,只有一种BST,即空树, f(0)=1 。
如果数组仅有一个元素{1},只有一种BST,单个节点, f(1)=1 。
如果数组有两个元素{1,2}, 那么有如下两种可能
1 2
\ /
2 1
再看一看3个元素的数组,可以发现BST的取值方式如下:
180
Unique Binary Search Trees
所以,由此观察,可以得出 f 的递推公式为
f (i) = ∑ik=1 f (k − 1) × f (i − k)
至此,问题划归为一维动态规划。
代码
f[0] = 1;
f[1] = 1;
for (int i = 2; i <= n; ++i) {
for (int k = 1; k <= i; ++k)
f[i] += f[k-1] * f[i - k];
}
return f[n];
}
};
相关题目
Unique Binary Search Trees II
181
Unique Binary Search Trees II
描述
Given n , generate all structurally unique BST's (binary search trees) that store values 1...n.
For example, Given n = 3 , your program should return all 5 unique BST's shown below.
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
分析
见前面一题。
代码
182
Unique Binary Search Trees II
相关题目
Unique Binary Search Trees
183
Validate Binary Search Tree
描述
Given a binary tree, determine if it is a valid binary search tree (BST).
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.
分析
无
代码
相关题目
Validate Binary Search Tree
184
Convert Sorted Array to Binary Search Tree
描述
Given an array where elements are sorted in ascending order, convert it to a height balanced BST.
分析
二分法。
代码
template
TreeNode* sortedArrayToBST (RandomAccessIterator first,
RandomAccessIterator last) {
const auto length = distance(first, last);
return root;
}
};
相关题目
Convert Sorted List to Binary Search Tree
185
Convert Sorted List to Binary Search Tree
描述
Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced
BST.
分析
这题与上一题类似,但是单链表不能随机访问,而自顶向下的二分法必须需要RandomAccessIterator,因
此前面的方法不适用本题。
存在一种自底向上(bottom-up)的方法,见 http://leetcode.com/2010/11/convert-sorted-list-to-balanced-
binary.html
分治法,自顶向下
分治法,类似于 Convert Sorted Array to Binary Search Tree,自顶向下,复杂度 O(nlogn) 。
186
Convert Sorted List to Binary Search Tree
自底向上
187
Convert Sorted List to Binary Search Tree
相关题目
Convert Sorted Array to Binary Search Tree
188
LCA of BST
LCA of BST
描述
Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST.
_______6______
/ \
___2__ ___8__
/ \ / \
1 _4 7 9
/ \
3 5
For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes
2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
分析
根据二叉搜索树的性质,两个子节点 p , q 和根节点 root 的关系,有以下四种情况:
1. 两个子节点都在树的左子树上
2. 两个子节点都在树的右子树上
3. 一个子节点在左子树,一个子节点在右子树
4. 一个子节点的值和根节点的值相等
以题目中的树为例,节点1和节点4为情况1,节点7和节点9为情况2,节点1和节点7为情况3,节点2和4为
情况4。 若为情况3或4,当前节点即为最近公共祖先,若为情况1或2,则还需递归到左或右子树上,继续
这个过程。
解法1 递归
解法2 迭代
相关题目
LCA of Binary Tree
189
Kth Smallest Element in a BST
描述
Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.
Note:
Follow up:
What if the BST is modified (insert/delete operations) often and you need to find the kth smallest
frequently? How would you optimize the kthSmallest routine?
Hint:
分析
最简单的办法,中序遍历,即可以得到递增序列,从而可以找到第k大的元素。时间复杂度 O(k) 。
若 k == root.leftCnt+1 , 则返回root
若 k > node.leftCnt , 则 k -= root.leftCnt+1 , root=root.right
否则, node = node.left
解法1
190
二叉树的递归
二叉树是一个递归的数据结构,因此是一个用来考察递归思维能力的绝佳数据结构。
递归一定是深搜(见 深搜与递归的区别),由于在二叉树上,递归的味道更浓些,因此本节用“二叉树的递
归”作为标题,而不是“二叉树的深搜”,尽管本节所有的算法都属于深搜。
二叉树的先序、中序、后序遍历都可以看做是DFS,此外还有其他顺序的深度优先遍历,共有 3!=6 种。
其他3种顺序是 root->r->l,r->root->l, r->l->root 。
191
Minimum Depth of Binary Tree
描述
Given a binary tree, find its minimum depth.
The minimum depth is the number of nodes along the shortest path from the root node down to the
nearest leaf node.
分析
无
递归版
迭代版
192
Minimum Depth of Binary Tree
stack<pair<TreeNode*, int>> s;
s.push(make_pair(root, 1));
while (!s.empty()) {
auto node = s.top().first;
auto depth = s.top().second;
s.pop();
return result;
}
};
相关题目
Maximum Depth of Binary Tree
193
Maximum Depth of Binary Tree
描述
Given a binary tree, find its maximum depth.
The maximum depth is the number of nodes along the longest path from the root node down to the
farthest leaf node.
分析
无
代码
相关题目
Minimum Depth of Binary Tree
194
Path Sum
Path Sum
描述
Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the
values along the path equals the given sum.
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
分析
题目只要求返回 true 或者 false ,因此不需要记录路径。
由于只需要求出一个结果,因此,当左、右任意一棵子树求到了满意结果,都可以及时return。
由于题目没有说节点的数据一定是正整数,必须要走到叶子节点才能判断,因此中途没法剪枝,只能进行
朴素深搜。
代码
// Path Sum
// 时间复杂度O(n),空间复杂度O(logn)
class Solution {
public:
bool hasPathSum(TreeNode *root, int sum) {
if (root == nullptr) return false;
相关题目
Path Sum II
195
Path Sum
196
Path Sum II
Path Sum II
描述
Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum.
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
return
[
[5,4,11,2],
[5,8,4,5]
]
分析
跟上一题相比,本题是求路径本身。且要求出所有结果,左子树求到了满意结果,不能return,要接着求右
子树。
代码
197
Path Sum II
// Path Sum II
// 时间复杂度O(n),空间复杂度O(logn)
class Solution {
public:
vector<vector<int> > pathSum(TreeNode *root, int sum) {
vector<vector<int> > result;
vector<int> cur; // 中间结果
pathSum(root, sum, cur, result);
return result;
}
private:
void pathSum(TreeNode *root, int gap, vector<int> &cur,
vector<vector<int> > &result) {
if (root == nullptr) return;
cur.push_back(root->val);
cur.pop_back();
}
};
相关题目
Path Sum
198
Binary Tree Maximum Path Sum
描述
Given a binary tree, find the maximum path sum.
The path may start and end at any node in the tree. For example: Given the below binary tree,
1
/ \
2 3
Return 6 .
分析
这题很难,路径可以从任意节点开始,到任意节点结束。
可以利用“最大连续子序列和”问题的思路,见这节Maximum Subarray。如果说Array只有一个方向的话,那
么Binary Tree其实只是左、右两个方向而已,我们需要比较两个方向上的值。
代码
199
Binary Tree Maximum Path Sum
注意,最后return的时候,只返回一个方向上的值,为什么?这是因为在递归中,只能向父节点返回,不可
能存在L->root->R的路径,只可能是L->root或R->root。
相关题目
Maximum Subarray
Maximum Product Subarray
200
Populating Next Right Pointers in Each Node
描述
Given a binary tree
struct TreeLinkNode {
int val;
TreeLinkNode *left, *right, *next;
TreeLinkNode(int x) : val(x), left(NULL), right(NULL), next(NULL) {}
};
Populate each next pointer to point to its next right node. If there is no next right node, the next pointer
should be set to NULL .
Note:
For example,
1
/ \
2 3
/ \ / \
4 5 6 7
1 -> NULL
/ \
2 -> 3 -> NULL
/ \ / \
4->5->6->7 -> NULL
分析
无
代码
201
Populating Next Right Pointers in Each Node
connect(root->left, root->right);
if (sibling)
connect(root->right, sibling->left);
else
connect(root->right, nullptr);
}
};
相关题目
Populating Next Right Pointers in Each Node II
202
Sum Root to Leaf Numbers
描述
Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number.
An example is the root-to-leaf path 1->2->3 which represents the number 123 .
For example,
1
/ \
2 3
The root-to-leaf path 1->2 represents the number 12 . The root-to-leaf path 1->3 represents the
number 13 .
分析
无
代码
203
LCA of 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.
分析
用自底向上(bottom-up)的思路,先看看是否能在 root 的左子树中找到 p 或 q ,再看看能否在右子树中
找到,
如果两边都能找到,说明当前节点就是最近公共祖先
如果左边没找到,则说明 p 和 q 都在右子树
如果右边没找到,则说明 p 和 q 都在左子树
代码
相关题目
LCA of BST
204
线段树
本节主要讲线段树。
205
Range Sum Query - Mutable
描述
Given an integer array nums , find the sum of the elements between indices i and j ( i ≤ j ),
inclusive.
The update(i, val) function modifies nums by updating the element at index i to val .
Example:
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
Note:
分析
由于需要求任意段的和,且会随机修改元素,用线段树(Segment Tree)再合适不过了。
解法1 线段树
解法2 树状数组
相关题目
Range Sum Query - Immutable
Range Sum Query 2D - Immutable
206
排序
排序
本章主要讲解各种排序算法。
207
插入排序
本节主要讲插入排序。
208
Insertion Sort List
描述
Sort a linked list using insertion sort.
分析
无
代码
相关题目
Sort List
209
归并排序
本节主要讲归并排序。
210
Merge Two Sorted Arrays
描述
Given two sorted integer arrays A and B, merge B into A as one sorted array.
Note: You may assume that A has enough space to hold additional elements from B. The number of
elements initialized in A and B are m and n respectively.
分析
无
代码
相关题目
Merge Two Sorted Lists
Merge k Sorted Lists
211
Merge Two Sorted Lists
描述
Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together
the nodes of the first two lists.
分析
无
代码
相关题目
Merge Two Sorted Arrays
Merge k Sorted Lists
212
Merge k Sorted Lists
描述
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
分析
可以复用 Merge Two Sorted Lists 的函数
代码
ListNode *p = lists[0];
for (int i = 1; i < lists.size(); i++) {
p = mergeTwoLists(p, lists[i]);
}
return p;
}
相关题目
Merge Two Sorted Arrays
Merge Two Sorted Lists
213
Merge k Sorted Lists
214
Sort List
Sort List
描述
Sort a linked list in O(n log n) time using constant space complexity.
分析
常数空间且 O(nlogn) ,单链表适合用归并排序,双向链表适合用快速排序。本题可以复用 Merge Two
Sorted Lists 的代码。
代码
215
Sort List
// Sort List
// 归并排序,时间复杂度O(nlogn),空间复杂度O(1)
class Solution {
public:
ListNode *sortList(ListNode *head) {
if (head == NULL || head->next == NULL)return head;
相关题目
Insertion Sort List
216
Sort List
217
快速排序
本节主要讲快速排序。
218
Sort Colors
Sort Colors
描述
Given an array with n objects colored red, white or blue, sort them so that objects of the same color are
adjacent, with the colors in the order red, white and blue.
Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.
Note: You are not suppose to use the library's sort function for this problem.
Follow up:
First, iterate the array counting number of 0's, 1's, and 2's, then overwrite array with total number of 0's,
then 1's and followed by 2's.
Could you come up with an one-pass algorithm using only constant space?
分析
由于0, 1, 2 非常紧凑,首先想到计数排序(counting sort),但需要扫描两遍,不符合题目要求。
由于只有三种颜色,可以设置两个index,一个是red的index,一个是blue的index,两边往中间走。时间复
杂度 O(n) ,空间复杂度 O(1) 。
代码1
// Sort Colors
// Counting Sort
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
void sortColors(vector<int>& A) {
int counts[3] = { 0 }; // 记录每个颜色出现的次数
}
};
219
Sort Colors
代码2
// Sort Colors
// 双指针,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
void sortColors(vector<int>& A) {
// 一个是red的index,一个是blue的index,两边往中间走
int red = 0, blue = A.size() - 1;
代码3
// Sort Colors
// 重新实现 partition()
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
void sortColors(vector<int>& nums) {
partition(partition(nums.begin(), nums.end(), bind1st(equal_to<int>(), 0)),
nums.end(), bind1st(equal_to<int>(), 1));
}
private:
template<typename ForwardIterator, typename UnaryPredicate>
ForwardIterator partition(ForwardIterator first, ForwardIterator last,
UnaryPredicate pred) {
auto pos = first;
return pos;
}
};
相关题目
First Missing Positive
220
Sort Colors
221
Kth Largest Element in an Array
描述
Find the k -th largest element in an unsorted array.
Note:
分析
这题是一道很好的面试题目,
题目短小,很快就能说清题意
有很多种解法。从简单到复杂的解法都有,梯度均匀。
不需要预先知道特殊领域知识。
这题有很多思路:
思路4和5比较高效,可以接受,其他思路太慢了,不采纳。
思路4 partition
思路5 小根堆
222
桶排序
桶排序(Bucket Sort)的基本思路是:
223
First Missing Positive
描述
Given an unsorted integer array, find the first missing positive integer.
Your algorithm should run in O(n) time and uses constant space.
分析
本质上是桶排序(bucket sort),每当 A[i]!= i+1 的时候,将 A[i] 与 A[A[i]-1] 交换,直到无法交换
为止,终止条件是 A[i]== A[A[i]-1] 。
代码
相关题目
Sort Colors
224
First Missing Positive
225
计数排序
计数排序本质上是一种特殊的桶排序,当桶的个数最大的时候,就是计数排序。
226
H-Index
H-Index
描述
Given an array of citations (each citation is a non-negative integer) of a researcher, write a function to
compute the researcher's h-index.
According to the definition of h-index on Wikipedia: "A scientist has index h if h of his/her N papers have
at least h citations each, and the other N − h papers have no more than h citations each."
For example, given citations = [3, 0, 6, 1, 5] , which means the researcher has 5 papers in total
and each of them had received 3, 0, 6, 1, 5 citations respectively. Since the researcher has 3
papers with at least 3 citations each and the remaining two with no more than 3 citations each, his h-index
is 3.
Note: If there are several possible values for h , the maximum one is taken as the h-index.
分析
H-Index的含义是,如果一个人发表的所有论文中,有 h 篇论文分别被引用了至少 h 次,那么他的H-
Index就是 h 。
思路一:先从大到小排序,然后从前往后扫描,如果当前文章数(即当前下标+1)等于值本身,则返回当
前文章数作为 h-index;如果当前文章数大于值本身,则返回当前文章数-1作为H-Index, 因为当前文章的引
用数小于当前文章数,不能算在内。时间复杂度O(n log n,空间复杂度O(1)。
思路二:跟思路一类似,不过排序算法换成了计数排序。有一个小技巧,因为H-Index最大不可能超过论文
综述,所以我们只需要开一个长度为 n+1 的数组,如果某篇论文的引用数超过了 n ,就将其当做 n 。
代码1 全排序
代码2 计数排序
相关题目
H-Index II
227
基数排序
1. 将所有待排序整数(注意,必须是非负整数)统一为位数相同的整数,位数较少的前面补零。一般用
10进制,也可以用16进制甚至2进制。所以前提是能够找到最大值,得到最长的位数,设 k 进制下最
长为位数为 d 。
2. 从最低位开始,依次进行一次稳定排序。这样从最低位一直到最高位排序完成以后,整个序列就变成
了一个有序序列。
为什么同一数位的排序子程序要用稳定排序?因为稳定排序能将上一次排序的成果保留下来。例如十位数
的排序过程能保留个位数的排序成果,百位数的排序过程能保留十位数的排序成果。
能不能用2进制?能,可以把待排序序列中的每个整数都看成是01组成的二进制数值。那这样的话,岂不是
任意一个非负整数序列都可以用基数排序算法?理论上是的,假设待排序序列中最大整数为26 4 − 1,则最
大位数 d=64 ,时间复杂度为 O(64n) 。可见任意一个非负整数序列都可以在线性时间内完成排序。
既然任意一个非负整数序列都可以在线性时间内完成排序,那么基于比较排序的算法有什么意义呢?基于
比较的排序算法,时间复杂度是 O(nlogn) ,看起来比 O(64n) 慢,仔细一想,其实不是, O(nlogn) 只
228
Maximum Gap
Maximum Gap
描述
Given an unsorted array, find the maximum difference between the successive elements in its sorted
form.
You may assume all elements in the array are non-negative integers and fit in the 32-bit signed integer
range.
分析
这道题最直接的解法是,先排序,得到有序数组,然后相邻元素相减,找出差最大的,时间复杂度 O(n
log n) 。
解法1 桶排序
解法2 基数排序
解法3 计数排序
计数排序本质上是一种特殊的桶排序,当桶的个数最大的时候,就是计数排序。
本题用计数排序会MLE。
229
其他
本节是一些排序相关的小技巧题目。
230
Largest Number
Largest Number
描述
Given a list of non negative integers, arrange them such that they form the largest number.
For example, given [3, 30, 34, 5, 9] , the largest formed number is 9534330 .
Note: The result may be very large, so you need to return a string instead of an integer.
分析
这题可以先把每个整数变成字符串,得到一个字符串数组,然后把这个数组按特定规则排个序,顺序输出
即可。
代码
231
小结
基数排序、桶排序和计数排序的区别
先比较时间复杂度和空间复杂度。
首先,基数排序和计数排序都可以看作是桶排序。
232
查找
查找
本章主要讲查找算法。
233
Search for a Range
描述
Given a sorted array of integers, find the starting and ending position of a given target value.
For example, Given [5, 7, 7, 8, 8, 10] and target value 8, return [3, 4] .
分析
已经排好了序,用二分查找。
234
Search for a Range
return first;
}
return first;
}
};
相关题目
Search Insert Position
235
Search Insert Position
描述
Given a sorted array and a target value, return the index if the target is found. If not, return the index
where it would be if it were inserted in order.
[1,3,5,6], 5 → 2
[1,3,5,6], 2 → 1
[1,3,5,6], 7 → 4
[1,3,5,6], 0 → 0
分析
即 std::lower_bound() 。
代码
return first;
}
};
相关题目
Search for a Range
236
Search Insert Position
237
Search in Rotated Sorted Array
描述
Suppose a sorted array is rotated at some pivot unknown to you beforehand.
You are given a target value to search. If found in the array return its index, otherwise return -1 .
分析
一个有序数组被循环右移,只可能有一下两种情况:
7 │
6 │
─────┼───────────
│ 5
│ 4
│ 3
│ 2
│ 1
7 │
6 │
5 │
4 │
3 │
───────────┼───────────
│ 2
│ 1
本题依旧可以用二分查找,难度主要在于左右边界的确定。仔细观察上面两幅图,我们可以得出如下结
论:
代码
238
Search in Rotated Sorted Array
相关题目
Search in Rotated Sorted Array II
Find Minimum in Rotated Sorted Array
Find Minimum in Rotated Sorted Array II
239
Search in Rotated Sorted Array II
描述
Follow up for "Search in Rotated Sorted Array": What if duplicates are allowed?
分析
允许重复元素,则上一题中如果 A[left] <= A[mid] ,那么 [left,mid] 为递增序列的假设就不能成立
了,比如 [1,3,1,1,1] 。
代码
240
Search in Rotated Sorted Array II
相关题目
Search in Rotated Sorted Array
Find Minimum in Rotated Sorted Array
Find Minimum in Rotated Sorted Array II
241
Search a 2D Matrix
Search a 2D Matrix
描述
Write an efficient algorithm that searches for a value in an m × n matrix. This matrix has the following
properties:
[
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
分析
二分查找。
代码
242
Search a 2D Matrix
// Search a 2D Matrix
// 时间复杂度O(logn),空间复杂度O(1)
class Solution {
public:
bool searchMatrix(const vector<vector<int>>& matrix, int target) {
if (matrix.empty()) return false;
const size_t m = matrix.size();
const size_t n = matrix.front().size();
int first = 0;
int last = m * n;
if (value == target)
return true;
else if (value < target)
first = mid + 1;
else
last = mid;
}
return false;
}
};
相关题目
Search a 2D Matrix II
243
Search a 2D Matrix II
Search a 2D Matrix II
描述
Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following
properties:
For example,
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
分析
从右上角开始, 比较 target 和 matrix[i][j] 的值。如果小于 target , 则该行不可能有此数, 所
以 i++ ; 如果大于 target , 则该列不可能有此数, 所以 j-- 。遇到边界则表明该矩阵不含 target .
代码
相关题目
Search a 2D Matrix
244
Find Minimum in Rotated Sorted Array
描述
Suppose a sorted array is rotated at some pivot unknown to you beforehand.
分析
从左向右扫描,扫描到的第一个逆序的位置,肯定是原始数组中第一个元素,时间复杂度 O(n) 。
不过本题依旧可以用二分查找,最关键的是要判断那个“断层”是在左边还是右边。
代码
相关题目
Search in Rotated Sorted Array
Search in Rotated Sorted Array II
Find Minimum in Rotated Sorted Array II
245
Find Minimum in Rotated Sorted Array II
描述
Follow up for "Find Minimum in Rotated Sorted Array":
分析
同 Find Minimum in Rotated Sorted Array 类似,要判断“断层”在左边还是右边。
本题还有另一种思路,
代码
相关题目
Search in Rotated Sorted Array
Search in Rotated Sorted Array II
Find Minimum in Rotated Sorted Array
246
Median of Two Sorted Arrays
描述
There are two sorted arrays A and B of size m and n respectively. Find the median of the two
sorted arrays. The overall run time complexity should be O(log (m+n)) .
分析
这是一道非常经典的题。这题更通用的形式是,给定两个已经排序好的数组,找到两者所有元素中第 k 大
的元素。
不过我们仅仅需要第 k 大的元素,是不需要“排序”这么昂贵的操作的。可以用一个计数器,记录当前已经
找到第 m 大的元素了。同时我们使用两个指针 pA 和 pB ,分别指向A和B数组的第一个元素,使用类似
于merge sort的原理,如果数组A当前元素小,那么 pA++ ,同时 m++ ;如果数组B当前元素小,那
么 pB++ ,同时 m++ 。最终当 m 等于 k 的时候,就得到了我们的答案, O(k) 时间, O(1) 空间。但
是,当 k 很接近 m+n 的时候,这个方法还是 O(m+n) 的。
A[k/2-1] == B[k/2-1]
A[k/2-1] > B[k/2-1]
A[k/2-1] < B[k/2-1]
因此,我们可以写一个递归函数。那么函数什么时候应该终止呢?
代码
247
Median of Two Sorted Arrays
248
H-Index II
H-Index II
描述
Follow up for H-Index: What if the citations array is sorted in ascending order? Could you optimize your
algorithm?
分析
设数组长度为 n ,那么 n-i 就是引用次数大于等于 nums[i] 的文章数。如果 nums[i]<n-i ,说
明 i 是有效的H-Index, 如果一个数是H-Index,那么最大的H-Index一定在它后面(因为是升序的),根据
这点就可以进行二分搜索了。
代码
249
暴力枚举法
本章的题目都能用暴力枚举法解决。
250
Subsets
Subsets
描述
Given a set of distinct integers, S , return all possible subsets.
Note:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
递归
增量构造法
每个元素,都有两种选择,选或者不选。
251
Subsets
// Subsets
// 增量构造法,深搜,时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S) {
sort(S.begin(), S.end()); // 输出要求有序
vector<vector<int> > result;
vector<int> path;
subsets(S, path, 0, result);
return result;
}
private:
static void subsets(const vector<int> &S, vector<int> &path, int step,
vector<vector<int> > &result) {
if (step == S.size()) {
result.push_back(path);
return;
}
// 不选S[step]
subsets(S, path, step + 1, result);
// 选S[step]
path.push_back(S[step]);
subsets(S, path, step + 1, result);
path.pop_back();
}
};
位向量法
开一个位向量 bool selected[n] ,每个元素可以选或者不选。
252
Subsets
// Subsets
// 位向量法,深搜,时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S) {
sort(S.begin(), S.end()); // 输出要求有序
private:
static void subsets(const vector<int> &S, vector<bool> &selected, int step,
vector<vector<int> > &result) {
if (step == S.size()) {
vector<int> subset;
for (int i = 0; i < S.size(); i++) {
if (selected[i]) subset.push_back(S[i]);
}
result.push_back(subset);
return;
}
// 不选S[step]
selected[step] = false;
subsets(S, selected, step + 1, result);
// 选S[step]
selected[step] = true;
subsets(S, selected, step + 1, result);
}
};
迭代
增量构造法
253
Subsets
// Subsets
// 迭代版,时间复杂度O(2^n),空间复杂度O(1)
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S) {
sort(S.begin(), S.end()); // 输出要求有序
vector<vector<int> > result(1);
for (auto elem : S) {
result.reserve(result.size() * 2);
auto half = result.begin() + result.size();
copy(result.begin(), half, back_inserter(result));
for_each(half, result.end(), [&elem](decltype(result[0]) &e){
e.push_back(elem);
});
}
return result;
}
};
二进制法
本方法的前提是:集合的元素不超过int位数。用一个int整数表示位向量,第 i 位为1,则表示选
择 S[i] ,为0则不选择。例如 S={A,B,C,D} ,则 0110=6 表示子集 {B,C} 。
这种方法最巧妙。因为它不仅能生成子集,还能方便的表示集合的并、交、差等集合运算。设两个集合的
位向量分别为B1和B2 ,则B1 ∪ B2 , B1 ∩ B2 , B1 △B2分别对应集合的并、交、对称差。
二进制法,也可以看做是位向量法,只不过更加优化。
// Subsets
// 二进制法,时间复杂度O(2^n),空间复杂度O(1)
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S) {
sort(S.begin(), S.end()); // 输出要求有序
vector<vector<int> > result;
const size_t n = S.size();
vector<int> v;
254
Subsets
相关题目
Subsets II
255
Subsets II
Subsets II
描述
Given a collection of integers that might contain duplicates, S , return all possible subsets.
Note:
Elements in a subset must be in non-descending order. The solution set must not contain duplicate
subsets. For example, If S = [1,2,2] , a solution is:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
分析
这题有重复元素,但本质上,跟上一题很类似,上一题中元素没有重复,相当于每个元素只能选0或1次,
这里扩充到了每个元素可以选0到若干次而已。
递归
增量构造法
256
Subsets II
// Subsets II
// 增量构造法,版本1,时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > subsetsWithDup(vector<int> &S) {
sort(S.begin(), S.end()); // 必须排序
private:
static void dfs(const vector<int> &S, vector<int>::iterator start,
vector<int> &path, vector<vector<int> > &result) {
result.push_back(path);
257
Subsets II
// Subsets II
// 增量构造法,版本2,时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > subsetsWithDup(vector<int> &nums) {
vector<vector<int> > result;
sort(nums.begin(), nums.end()); // 必须排序
// 将map里的pair拷贝到一个vector里
vector<pair<int, int> > elems;
for (auto p : count_map) {
elems.push_back(p);
}
sort(elems.begin(), elems.end());
vector<int> path; // 中间结果
private:
static void dfs(const vector<pair<int, int> > &elems,
size_t step, vector<int> &path, vector<vector<int> > &result) {
if (step == elems.size()) {
result.push_back(path);
return;
}
位向量法
// Subsets II
258
Subsets II
// 位向量法,时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > subsetsWithDup(vector<int> &nums) {
vector<vector<int> > result; // 必须排序
sort(nums.begin(), nums.end());
// 记录每个元素的出现次数
unordered_map<int, int> count_map;
for (int i : nums) {
if (count_map.find(i) != count_map.end())
count_map[i]++;
else
count_map[i] = 1;
}
// 将map里的pair拷贝到一个vector里
vector<pair<int, int> > counters;
for (auto p : count_map) {
counters.push_back(p);
}
sort(counters.begin(), counters.end());
// 每个元素选择了多少个
unordered_map<int, int> selected;
for (auto p : counters) {
selected[p.first] = 0;
}
private:
static void dfs(const vector<int> &S, const vector<pair<int, int> >& counters,
unordered_map<int, int>& selected, size_t step, vector<vector<int> > &resul
t) {
if (step == counters.size()) {
vector<int> subset;
for (auto p : counters) {
for (int i = 0; i < selected[p.first]; ++i) {
subset.push_back(p.first);
}
}
result.push_back(subset);
return;
}
259
Subsets II
迭代
增量构造法
// Subsets II
// 增量构造法
// 时间复杂度O(2^n),空间复杂度O(1)
class Solution {
public:
vector<vector<int> > subsetsWithDup(vector<int> &S) {
sort(S.begin(), S.end()); // 必须排序
vector<vector<int> > result(1);
size_t previous_size = 0;
for (size_t i = 0; i < S.size(); ++i) {
const size_t size = result.size();
for (size_t j = 0; j < size; ++j) {
if (i == 0 || S[i] != S[i-1] || j >= previous_size) {
result.push_back(result[j]);
result.back().push_back(S[i]);
}
}
previous_size = size;
}
return result;
}
};
二进制法
260
Subsets II
// Subsets II
// 二进制法,时间复杂度O(2^n),空间复杂度O(1)
class Solution {
public:
vector<vector<int> > subsetsWithDup(vector<int> &S) {
sort(S.begin(), S.end()); // 必须排序
// 用 set 去重,不能用 unordered_set,因为输出要求有序
set<vector<int> > result;
const size_t n = S.size();
vector<int> v;
相关题目
Subsets
261
Permutations
Permutations
描述
Given a collection of numbers, return all possible permutations.
For example, [1,2,3] have the following permutations: [1,2,3], [1,3,2], [2,1,3], [2,3,1],
[3,1,2] , and [3,2,1] .
next_permutation()
函数 next_permutation() 的具体实现见这节 Next Permutation。
// Permutations
// 重新实现 next_permutation()
// 时间复杂度O(n!),空间复杂度O(1)
class Solution {
public:
vector<vector<int> > permute(vector<int> &num) {
vector<vector<int> > result;
sort(num.begin(), num.end());
do {
result.push_back(num);
// 调用的是 2.1.12 节的 next_permutation()
// 而不是 std::next_permutation()
} while(next_permutation(num.begin(), num.end()));
return result;
}
private:
// 代码来自 2.1.12 节的 next_permutation()
void nextPermutation(vector<int> &nums) {
next_permutation(nums.begin(), nums.end());
}
template<typename BidiIt>
bool next_permutation(BidiIt first, BidiIt last) {
// Get a reversed range to simplify reversed traversal.
const auto rfirst = reverse_iterator<BidiIt>(last);
const auto rlast = reverse_iterator<BidiIt>(first);
// Find `pivot`, which is the first element that is no less than its
// successor. `Prev` is used since `pivort` is a `reversed_iterator`.
while (pivot != rlast && *pivot >= *prev(pivot))
++pivot;
262
Permutations
// Scan from right to left, find the first element that is greater than
// `pivot`.
auto change = find_if(rfirst, pivot, bind1st(less<int>(), *pivot));
swap(*change, *pivot);
reverse(rfirst, pivot);
return true;
}
};
递归
本题是求路径本身,求所有解,函数参数需要标记当前走到了哪步,还需要中间结果的引用,最终结果的
引用。
扩展节点,每次从左到右,选一个没有出现过的元素。
本题不需要判重,因为状态装换图是一颗有层次的树。收敛条件是当前走到了最后一个元素。
代码
263
Permutations
// Permutations
// 深搜,增量构造法
// 时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > permute(vector<int>& num) {
sort(num.begin(), num.end());
vector<vector<int>> result;
vector<int> path; // 中间结果
// 扩展状态
for (auto i : num) {
// 查找 i 是否在path 中出现过
auto pos = find(path.begin(), path.end(), i);
if (pos == path.end()) {
path.push_back(i);
dfs(num, path, result);
path.pop_back();
}
}
}
};
相关题目
Next Permutation
Permutation Sequence
Permutations II
Combinations
264
Permutations II
Permutations II
描述
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
For example, [1,1,2] have the following unique permutations: [1,1,2], [1,2,1] , and [2,1,1] .
next_permutation()
直接使用 std::next_permutation() ,代码与上一题相同。
重新实现next_permutation()
重新实现 std::next_permutation() ,代码与上一题相同。
递归
递归函数 permute() 的参数 p ,是中间结果,它的长度又能标记当前走到了哪一步,用于判断收敛条
件。
扩展节点,每次从小到大,选一个没有被用光的元素,直到所有元素被用光。
本题不需要判重,因为状态装换图是一颗有层次的树。
// Permutations II
// 深搜,时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
// 将map里的pair拷贝到一个vector里
vector<pair<int, int> > counters;
for (auto p : count_map) {
counters.push_back(p);
}
sort(counters.begin(), counters.end());
// 每个元素选择了多少个
unordered_map<int, int> selected;
265
Permutations II
n = nums.size();
permute(counters, selected, p, result);
return result;
}
private:
size_t n;
typedef vector<pair<int, int> >::const_iterator Iter;
// 扩展状态
for (auto counter : counters) {
if (selected[counter.first] < counter.second) {
p.push_back(counter.first);
selected[counter.first]++;
permute(counters, selected, p, result);
p.pop_back(); // 撤销动作,返回上一层
selected[counter.first]--;
}
}
}
};
相关题目
Next Permutation
Permutation Sequence
Permutations
Combinations
266
Combinations
Combinations
描述
Given two integers n and k , return all possible combinations of k numbers out of 1 ... n .
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
递归
// Combinations
// 深搜,递归
// 时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > combine(int n, int k) {
vector<vector<int> > result;
vector<int> path;
dfs(n, k, 1, 0, path, result);
return result;
}
private:
// start,开始的数, cur,已经选择的数目
static void dfs(int n, int k, int start, int cur,
vector<int> &path, vector<vector<int> > &result) {
if (cur == k) {
result.push_back(path);
}
for (int i = start; i <= n; ++i) {
path.push_back(i);
dfs(n, k, i + 1, cur + 1, path, result);
path.pop_back();
}
}
};
相关题目
Next Permutation
267
Combinations
Permutation Sequence
Permutations
Permutations II
268
Letter Combinations of a Phone Number
描述
Given a digit string, return all possible letter combinations that the number could represent.
A mapping of digit to letters (just like on the telephone buttons) is given below.
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] .
Note: Although the above answer is in lexicographical order, your answer could be in any order you want.
分析
无
递归
269
Letter Combinations of a Phone Number
迭代
270
Letter Combinations of a Phone Number
// resize to n * m
for (size_t i = 1; i < m; ++i) {
for (size_t j = 0; j < n; ++j) {
result.push_back(result[j]);
}
}
271
广度优先搜索
广度优先搜索
当题目看不出任何规律,既不能用分治,贪心,也不能用动规时,这时候万能方法——搜索, 就派上用场
了。搜索分为广搜和深搜,广搜里面又有普通广搜,双向广搜,A*搜索等。 深搜里面又有普通深搜,回溯
法等。
广搜和深搜非常类似(除了在扩展节点这部分不一样),二者有相同的框架,如何表示状态? 如何扩展状
态?如何判重?尤其是判重,解决了这个问题,基本上整个问题就解决了。
272
Word Ladder
Word Ladder
描述
Given two words (start and end), and a dictionary, find the length of shortest transformation sequence
from start to end, such that:
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:
分析
求最短路径,用广搜。
单队列
// Word Ladder
// 时间复杂度O(n),空间复杂度O(n)
struct state_t {
string word;
int level;
namespace std {
template<> struct hash<state_t> {
273
Word Ladder
public:
size_t operator()(const state_t& s) const {
return str_hash(s.word);
}
private:
std::hash<std::string> str_hash;
};
}
class Solution {
public:
int ladderLength(const string& start, const string &end,
const unordered_set<string> &dict) {
queue<state_t> q;
unordered_set<state_t> visited; // 判重
swap(c, new_state.word[i]);
if (state_is_valid(new_state) &&
visited.find(new_state) == visited.end()) {
result.insert(new_state);
}
swap(c, new_state.word[i]); // 恢复该单词
}
}
return result;
};
if (state_is_target(state)) {
274
Word Ladder
return state.level + 1;
}
双队列
// Word Ladder
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
int ladderLength(const string& start, const string &end,
const unordered_set<string> &dict) {
queue<string> current, next; // 当前层,下一层
unordered_set<string> visited; // 判重
swap(c, new_word[i]);
if (state_is_valid(new_word) &&
visited.find(new_word) == visited.end()) {
result.insert(new_word);
}
swap(c, new_word[i]); // 恢复该单词
}
}
return result;
};
275
Word Ladder
current.push(start);
visited.insert(start);
while (!current.empty()) {
++level;
while (!current.empty()) {
// 千万不能用 const auto&,pop() 会删除元素,
// 引用就变成了悬空引用
const auto state = current.front();
current.pop();
if (state_is_target(state)) {
return level + 1;
}
相关题目
Word Ladder II
276
Word Ladder II
Word Ladder II
描述
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start
to end, such that:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
Note:
分析
跟 Word Ladder比,这题是求路径本身,不是路径长度,也是BFS,略微麻烦点。
求一条路径和求所有路径有很大的不同,求一条路径,每个状态节点只需要记录一个前驱即可;求所有路
径时,有的状态节点可能有多个父节点,即要记录多个前驱。
如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的处理,因为我们要找的是最短路径。
单队列
// Word Ladder II
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
vector<vector<string> > findLadders(const string& start,
const string& end, const unordered_set<string> &dict) {
queue<string> q;
unordered_map<string, int> visited; // 判重
unordered_map<string, vector<string> > father; // DAG
277
Word Ladder II
swap(c, new_state[i]);
if (state_is_valid(new_state)) {
auto visited_iter = visited.find(new_state);
if (visited_iter != visited.end()) {
const int depth = visited_iter->second;
if (depth < new_depth) {
// do nothing
}
else if (depth == new_depth) {
result.insert(new_state);
}
else { // not possible
throw std::logic_error("not possible to get here");
}
}
else {
result.insert(new_state);
}
}
swap(c, new_state[i]); // 恢复该单词
}
}
return result;
};
vector<vector<string>> result;
q.push(start);
visited[start] = 0;
while (!q.empty()) {
// 千万不能用 const auto&,pop() 会删除元素,
// 引用就变成了悬空引用
const auto state = q.front();
q.pop();
// 如果当前路径长度已经超过当前最短路径长度,
278
Word Ladder II
// 可以中止对该路径的处理,因为我们要找的是最短路径
if (!result.empty() && visited[state] + 1 > result[0].size()) break;
if (state_is_target(state)) {
vector<string> path;
gen_path(father, start, state, path, result);
continue;
}
// 必须挪到下面,比如同一层A和B两个节点均指向了目标节点,
// 那么目标节点就会在q中出现两次,输出路径就会翻倍
// visited.insert(state);
// 扩展节点
const auto& new_states = state_extend(state);
for (const auto& new_state : new_states) {
if (visited.find(new_state) == visited.end()) {
q.push(new_state);
visited[new_state] = visited[state] + 1;
}
father[new_state].push_back(state);
}
}
return result;
}
private:
void gen_path(unordered_map<string, vector<string> > &father,
const string &start, const string &state, vector<string> &path,
vector<vector<string> > &result) {
path.push_back(state);
if (state == start) {
if (!result.empty()) {
if (path.size() < result[0].size()) {
result.clear();
}
else if (path.size() == result[0].size()) {
// do nothing
}
else { // not possible
throw std::logic_error("not possible to get here ");
}
}
result.push_back(path);
reverse(result.back().begin(), result.back().end());
}
else {
for (const auto& f : father[state]) {
gen_path(father, start, f, path, result);
}
}
path.pop_back();
}
};
279
Word Ladder II
双队列
// Word Ladder II
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
vector<vector<string> > findLadders(const string& start,
const string& end, const unordered_set<string> &dict) {
// 当前层,下一层,用unordered_set是为了去重,例如两个父节点指向
// 同一个子节点,如果用vector, 子节点就会在next里出现两次,其实此
// 时 father 已经记录了两个父节点,next里重复出现两次是没必要的
unordered_set<string> current, next;
unordered_set<string> visited; // 判重
unordered_map<string, vector<string> > father; // DAG
swap(c, new_word[i]);
if (state_is_valid(new_word) &&
visited.find(new_word) == visited.end()) {
result.insert(new_word);
}
swap(c, new_word[i]); // 恢复该单词
}
}
return result;
};
280
Word Ladder II
// 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
// 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
// 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
for (const auto& state : current)
visited.insert(state);
for (const auto& state : current) {
if (state_is_target(state)) {
vector<string> path;
gen_path(father, path, start, state, result);
continue;
}
current.clear();
swap(current, next);
}
return result;
}
private:
void gen_path(unordered_map<string, vector<string> > &father,
vector<string> &path, const string &start, const string &word,
vector<vector<string> > &result) {
path.push_back(word);
if (word == start) {
if (!result.empty()) {
if (path.size() < result[0].size()) {
result.clear();
result.push_back(path);
} else if(path.size() == result[0].size()) {
result.push_back(path);
} else {
// not possible
throw std::logic_error("not possible to get here");
}
} else {
result.push_back(path);
}
reverse(result.back().begin(), result.back().end());
} else {
for (const auto& f : father[word]) {
gen_path(father, path, start, f, result);
}
}
path.pop_back();
}
};
281
Word Ladder II
图的广搜
前面的解法,在状态扩展的时候,每次都是从'a'到'z'全部枚举一遍,重复计算,比较浪费,其实当给定字
典 dict 后,单词与单词之间的路径就固定下来了,本质上单词与单词之间构成了一个无向图。如果事先
把这个图构建出来,那么状态扩展就会大大加快。
// Word Ladder II
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
vector<vector<string> > findLadders(const string& start,
const string& end, const unordered_set<string> &dict) {
queue<string> q;
unordered_map<string, int> visited; // 判重
unordered_map<string, vector<string> > father; // DAG
// only used by state_extend()
const unordered_map<string, unordered_set<string> >& g = build_graph(dict);
return result;
};
282
Word Ladder II
vector<vector<string>> result;
q.push(start);
visited[start] = 0;
while (!q.empty()) {
// 千万不能用 const auto&,pop() 会删除元素,
// 引用就变成了悬空引用
const auto state = q.front();
q.pop();
// 如果当前路径长度已经超过当前最短路径长度,
// 可以中止对该路径的处理,因为我们要找的是最短路径
if (!result.empty() && visited[state] + 1 > result[0].size()) break;
if (state_is_target(state)) {
vector<string> path;
gen_path(father, start, state, path, result);
continue;
}
// 必须挪到下面,比如同一层A和B两个节点均指向了目标节点,
// 那么目标节点就会在q中出现两次,输出路径就会翻倍
// visited.insert(state);
// 扩展节点
const auto& new_states = state_extend(state);
for (const auto& new_state : new_states) {
if (visited.find(new_state) == visited.end()) {
q.push(new_state);
visited[new_state] = visited[state] + 1;
}
father[new_state].push_back(state);
}
}
return result;
}
private:
void gen_path(unordered_map<string, vector<string> > &father,
const string &start, const string &state, vector<string> &path,
vector<vector<string> > &result) {
path.push_back(state);
if (state == start) {
if (!result.empty()) {
if (path.size() < result[0].size()) {
result.clear();
}
else if (path.size() == result[0].size()) {
// do nothing
}
else { // not possible
throw std::logic_error("not possible to get here ");
}
}
283
Word Ladder II
result.push_back(path);
reverse(result.back().begin(), result.back().end());
}
else {
for (const auto& f : father[state]) {
gen_path(father, start, f, path, result);
}
}
path.pop_back();
}
unordered_map<string, unordered_set<string> > build_graph(
const unordered_set<string>& dict) {
unordered_map<string, unordered_set<string> > adjacency_list;
swap(c, new_word[i]);
if ((dict.find(new_word) != dict.end())) {
auto iter = adjacency_list.find(word);
if (iter != adjacency_list.end()) {
iter->second.insert(new_word);
}
else {
adjacency_list.insert(pair<string,
unordered_set<string >> (word, unordered_set<string>())
);
adjacency_list[word].insert(new_word);
}
}
swap(c, new_word[i]); // 恢复该单词
}
}
}
return adjacency_list;
}
};
相关题目
Word Ladder
284
Surrounded Regions
Surrounded Regions
描述
Given a 2D board containing 'X' and 'O' , capture all regions surrounded by 'X' .
A region is captured by flipping all 'O' s into 'X' s in that surrounded region .
For example,
X X X X
X O O X
X X O X
X O X X
X X X X
X X X X
X X X X
X O X X
分析
广搜。从上下左右四个边界往里走,凡是能碰到的 'O' ,都是跟边界接壤的,应该保留。
代码
285
Surrounded Regions
board[i][j] = 'X';
else if (board[i][j] == '+')
board[i][j] = 'O';
}
private:
void bfs(vector<vector<char>> &board, int i, int j) {
typedef pair<int, int> state_t;
queue<state_t> q;
const int m = board.size();
const int n = board[0].size();
return result;
};
state_t start = { i, j };
if (state_is_valid(start)) {
board[i][j] = '+';
q.push(start);
}
while (!q.empty()) {
auto cur = q.front();
q.pop();
auto new_states = state_extend(cur);
for (auto s : new_states) q.push(s);
}
}
};
286
Surrounded Regions
287
总结
小结
适用场景
输入数据:没什么特征,不像深搜,需要有“递归”的性质。如果是树或者图,概率更大。
状态转换图:树或者DAG图。
求解目标:多阶段最优化问题。
思考的步骤
1. 是求路径长度,还是路径本身(或动作序列)?
i. 如果是求路径长度,则状态里面要存路径长度(或双队列+一个全局变量)
ii. 如果是求路径本身或动作序列
i. 要用一棵树存储宽搜过程中的路径
ii. 是否可以预估状态个数的上限?能够预估状态总数,则开一个大数组,用树的双亲表示法;如
果不能预估状态总数,则要使用一棵通用的树。这一步也是第4步的必要不充分条件。
2. 如何表示状态?即一个状态需要存储哪些些必要的数据,才能够完整提供如何扩展到下一步状态的所
有信息。一般记录当前位置或整体局面。
3. 如何扩展状态?这一步跟第2步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数据
结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,
对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。
4. 如何判断重复?如果状态转换图是一颗树,则永远不会出现回路,不需要判重;如果状态转换图是一
个图(这时候是一个图上的BFS),则需要判重。
i. 如果是求最短路径长度或一条路径,则只需要让“点”(即状态)不重复出现,即可保证不出现回路
ii. 如果是求所有路径,注意此时,状态转换图是DAG,即允许两个父节点指向同一个子节点。具体
实现时,每个节点要“延迟”加入到已访问集合 visited ,要等一层全部访问完后,再加入
到 visited 集合。
iii. 具体实现
i. 状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。
ii. 如果不存在,则需要使用通用的哈希表(自己实现或用标准库,例如 unordered_set )来判
重;自己实现哈希表的话,如果能够预估状态个数的上限,则可以开两个数组,head和
next,表示哈希表,参考第 ??? 节方案2。
iii. 如果存在,则可以开一个大布尔数组,来判重,且此时可以精确计算出状态总数,而不仅仅是
预估上限。
5. 目标状态是否已知?如果题目已经给出了目标状态,可以带来很大便利,这时候可以从起始状态出
发,正向广搜;也可以从目标状态出发,逆向广搜;也可以同时出发,双向广搜。
代码模板
288
总结
广搜需要一个队列,用于一层一层扩展,一个hashset,用于判重,一棵树(只求长度时不需要),用于存
储整棵树。
如何表示状态
/** 状态 */
struct state_t {
int data1; /** 状态的数据,可以有多个字段. */
int data2; /** 状态的数据,可以有多个字段. */
// dataN; /** 其他字段 */
int action; /** 由父状态移动到本状态的动作,求动作序列时需要. */
int level; /** 所在的层次(从0开始),也即路径长度-1,求路径长度时需要;
不过,采用双队列时不需要本字段,只需全局设一个整数 */
bool operator==(const state_t &other) const {
return true; // 根据具体问题实现
}
};
// 定义hash函数
// 方法1:模板特化,当hash函数只需要状态本身,不需要其他数据时,用这个方法比较简洁
namespace std {
template<> struct hash<state_t> {
size_t operator()(const state_t & x) const {
return 0; // 根据具体问题实现
}
};
}
// 方法2:函数对象,如果hash函数需要运行时数据,则用这种方法
class Hasher {
public:
Hasher(int _m) : m(_m) {};
size_t operator()(const state_t &s) const {
return 0; // 根据具体问题实现
289
总结
}
private:
int m; // 存放外面传入的数据
};
/**
* @brief 反向生成路径,求一条路径.
* @param[in] father 树
* @param[in] target 目标节点
* @return 从起点到target的路径
*/
vector<state_t> gen_path(const unordered_map<state_t, state_t> &father,
const state_t &target) {
vector<state_t> path;
path.push_back(target);
reverse(path.begin(), path.end());
return path;
}
/**
* 反向生成路径,求所有路径.
* @param[in] father 存放了所有路径的树
* @param[in] start 起点
* @param[in] state 终点
* @return 从起点到终点的所有路径
*/
void gen_path(unordered_map<state_t, vector<state_t> > &father,
const string &start, const state_t& state, vector<state_t> &path,
vector<vector<state_t> > &result) {
path.push_back(state);
if (state == start) {
if (!result.empty()) {
if (path.size() < result[0].size()) {
result.clear();
result.push_back(path);
} else if(path.size() == result[0].size()) {
result.push_back(path);
} else {
// not possible
throw std::logic_error("not possible to get here");
}
} else {
result.push_back(path);
}
reverse(result.back().begin(), result.back().end());
} else {
for (const auto& f : father[state]) {
290
总结
求最短路径长度或一条路径
单队列的写法
#include "bfs_common.h"
/**
* @brief 广搜,只用一个队列.
* @param[in] start 起点
* @param[in] data 输入数据
* @return 从起点到目标状态的一条最短路径
*/
vector<state_t> bfs(state_t &start, const vector<vector<int>> &grid) {
queue<state_t> q; // 队列
unordered_set<state_t> visited; // 判重
unordered_map<state_t, state_t> father; // 树,求路径本身时才需要
// 判断状态是否合法
auto state_is_valid = [&](const state_t &s) { /*...*/ };
// 判断当前状态是否为所求目标
auto state_is_target = [&](const state_t &s) { /*...*/ };
// 扩展当前状态
auto state_extend = [&](const state_t &s) {
unordered_set<state_t> result;
for (/*...*/) {
const state_t new_state = /*...*/;
if (state_is_valid(new_state) &&
visited.find(new_state) != visited.end()) {
result.insert(new_state);
}
}
return result;
};
// 访问节点
291
总结
if (state_is_target(state)) {
return return gen_path(father, target); // 求一条路径
// return state.level + 1; // 求路径长度
}
// 扩展节点
vector<state_t> new_states = state_extend(state);
for (const auto& new_state : new_states) {
q.push(new_state);
father[new_state] = state; // 求一条路径
// visited.insert(state); // 优化:可以提前加入 visited 集合,
// 从而缩小状态扩展。这时 q 的含义略有变化,里面存放的是处理了一半
// 的节点:已经加入了visited,但还没有扩展。别忘记 while循环开始
// 前,要加一行代码, visited.insert(start)
}
}
return vector<state_t>();
//return 0;
}
双队列的写法
#include "bfs_common.h"
/**
* @brief 广搜,使用两个队列.
* @param[in] start 起点
* @param[in] data 输入数据
* @return 从起点到目标状态的一条最短路径
*/
vector<state_t> bfs(const state_t &start, const type& data) {
queue<state_t> next, current; // 当前层,下一层
unordered_set<state_t> visited; // 判重
unordered_map<state_t, state_t> father; // 树,求路径本身时才需要
// 判断状态是否合法
auto state_is_valid = [&](const state_t &s) { /*...*/ };
// 判断当前状态是否为所求目标
auto state_is_target = [&](const state_t &s) { /*...*/ };
// 扩展当前状态
auto state_extend = [&](const state_t &s) {
unordered_set<state_t> result;
for (/*...*/) {
const state_t new_state = /*...*/;
if (state_is_valid(new_state) &&
visited.find(new_state) != visited.end()) {
result.insert(new_state);
292
总结
}
}
return result;
};
current.push(start);
while (!current.empty()) {
++level;
while (!current.empty()) {
// 千万不能用 const auto&,pop() 会删除元素,
// 引用就变成了悬空引用
const auto state = current.front();
current.pop();
visited.insert(state);
if (state_is_target(state)) {
return return gen_path(father, state); // 求一条路径
// return state.level + 1; // 求路径长度
}
return vector<state_t>();
// return 0;
}
求所有路径
单队列
/**
* @brief 广搜,使用一个队列.
* @param[in] start 起点
* @param[in] data 输入数据
* @return 从起点到目标状态的所有最短路径
*/
vector<vector<state_t> > bfs(const state_t &start, const type& data) {
queue<state_t> q;
unordered_set<state_t> visited; // 判重
unordered_map<state_t, vector<state_t> > father; // DAG
293
总结
if (visited_iter != visited.end()) {
if (visited_iter->level < new_state.level) {
// do nothing
} else if (visited_iter->level == new_state.level) {
result.insert(new_state);
} else { // not possible
throw std::logic_error("not possible to get here");
}
} else {
result.insert(new_state);
}
}
}
return result;
};
vector<vector<string>> result;
state_t start_state(start, 0);
q.push(start_state);
visited.insert(start_state);
while (!q.empty()) {
// 千万不能用 const auto&,pop() 会删除元素,
// 引用就变成了悬空引用
const auto state = q.front();
q.pop();
// 如果当前路径长度已经超过当前最短路径长度,
// 可以中止对该路径的处理,因为我们要找的是最短路径
if (!result.empty() && state.level + 1 > result[0].size()) break;
if (state_is_target(state)) {
vector<string> path;
gen_path(father, start_state, state, path, result);
continue;
}
// 必须挪到下面,比如同一层A和B两个节点均指向了目标节点,
// 那么目标节点就会在q中出现两次,输出路径就会翻倍
// visited.insert(state);
// 扩展节点
const auto& new_states = state_extend(state);
for (const auto& new_state : new_states) {
if (visited.find(new_state) == visited.end()) {
294
总结
q.push(new_state);
}
visited.insert(new_state);
father[new_state].push_back(state);
}
}
return result;
}
双队列的写法
#include "bfs_common.h"
/**
* @brief 广搜,使用两个队列.
* @param[in] start 起点
* @param[in] data 输入数据
* @return 从起点到目标状态的所有最短路径
*/
vector<vector<state_t> > bfs(const state_t &start, const type& data) {
// 当前层,下一层,用unordered_set是为了去重,例如两个父节点指向
// 同一个子节点,如果用vector, 子节点就会在next里出现两次,其实此
// 时 father 已经记录了两个父节点,next里重复出现两次是没必要的
unordered_set<string> current, next;
unordered_set<state_t> visited; // 判重
unordered_map<state_t, vector<state_t> > father; // DAG
// 判断状态是否合法
auto state_is_valid = [&](const state_t &s) { /*...*/ };
// 判断当前状态是否为所求目标
auto state_is_target = [&](const state_t &s) { /*...*/ };
// 扩展当前状态
auto state_extend = [&](const state_t &s) {
unordered_set<state_t> result;
for (/*...*/) {
const state_t new_state = /*...*/;
if (state_is_valid(new_state) &&
visited.find(new_state) != visited.end()) {
result.insert(new_state);
}
}
return result;
};
295
总结
++ level;
// 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的
// 处理,因为我们要找的是最短路径
if (!result.empty() && level+1 > result[0].size()) break;
// 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
// 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
// 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
for (const auto& state : current)
visited.insert(state);
for (const auto& state : current) {
if (state_is_target(state)) {
vector<string> path;
gen_path(father, path, start, state, result);
continue;
}
current.clear();
swap(current, next);
}
return result;
}
296
深度优先搜索
本章主要讲各种深度优先搜索。
297
Additive Number
Additive Number
描述
Additive number is a string whose digits can form additive sequence.
A valid additive sequence should contain at least three numbers. Except for the first two numbers, each
subsequent number in the sequence must be the sum of the preceding two.
For example:
"112358" is an additive number because the digits can form an additive sequence: 1, 1, 2, 3, 5,
8 .
1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8
"199100199" is also an additive number, the additive sequence is: 1, 99, 100, 199 .
Note: Numbers in the additive sequence cannot have leading zeros, so sequence 1, 2, 03 or 1, 02,
3 is invalid.
Given a string containing only digits '0'-'9' , write a function to determine if it's an additive number.
Follow up:
How would you handle overflow for very large input integers?
分析
这是一个多阶段决策问题,且必须走到字符串最后一个字符才能得出结论,因此适合用深搜或DP。
再仔细想一下状态转换图,每次索引变化一下,就跟之前的完全没有重复,因此状态转换图是一颗树,不
是DAG,因此不存在重叠子问题,因此排除DP,本题应该用深搜。
代码
298
Palindrome Partitioning
Palindrome Partitioning
描述
Given a string s, partition s such that every substring of the partition is a palindrome.
[
["aa","b"],
["a","a","b"]
]
分析
在每一步都可以判断中间结果是否为合法结果,用回溯法。
深搜1
299
Palindrome Partitioning
// Palindrome Partitioning
// 时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> result;
vector<string> path; // 一个partition方案
dfs(s, path, result, 0, 1);
return result;
}
深搜2
另一种写法,更加简洁。这种写法也在 Combination Sum, Combination Sum II 中出现过。
300
Palindrome Partitioning
// Palindrome Partitioning
// 时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> result;
vector<string> path; // 一个partition方案
DFS(s, path, result, 0);
return result;
}
// 搜索必须以s[start]开头的partition方案
void DFS(string &s, vector<string>& path,
vector<vector<string>> &result, int start) {
if (start == s.size()) {
result.push_back(path);
return;
}
for (int i = start; i < s.size(); i++) {
if (isPalindrome(s, start, i)) { // 从i位置砍一刀
path.push_back(s.substr(start, i - start + 1));
DFS(s, path, result, i + 1); // 继续往下砍
path.pop_back(); // 撤销上上行
}
}
}
bool isPalindrome(const string &s, int start, int end) {
while (start < end && s[start] == s[end]) {
++start;
--end;
}
return start >= end;
}
};
动规
301
Palindrome Partitioning
// Palindrome Partitioning
// 动规,时间复杂度O(n^2),空间复杂度O(1)
class Solution {
public:
vector<vector<string> > partition(string s) {
const int n = s.size();
bool p[n][n]; // whether s[i,j] is palindrome
fill_n(&p[0][0], n * n, false);
for (int i = n - 1; i >= 0; --i)
for (int j = i; j < n; ++j)
p[i][j] = s[i] == s[j] && ((j - i < 2) || p[i + 1][j - 1]);
相关题目
Palindrome Partitioning II
302
Unique Paths
Unique Paths
描述
A robot is located at the top-left corner of a m × n grid (marked 'Start' in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-
right corner of the grid (marked 'Finish' in the diagram below).
Figure: Above is a `3 × 7` grid. How many possible unique paths are there?
深搜
// Unique Paths
// 深搜,小集合可以过,大集合会超时
// 时间复杂度O(n^4),空间复杂度O(n)
class Solution {
public:
int uniquePaths(int m, int n) {
if (m < 1 || n < 1) return 0; // 终止条件
备忘录法
给前面的深搜,加个缓存,就可以过大集合了。即备忘录法。
303
Unique Paths
// Unique Paths
// 深搜 + 缓存,即备忘录法
// 时间复杂度O(n^2),空间复杂度O(n^2)
class Solution {
public:
int uniquePaths(int m, int n) {
// f[x][y] 表示 从(0,0)到(x,y)的路径条数
f = vector<vector<int> >(m, vector<int>(n, 0));
f[0][0] = 1;
return dfs(m - 1, n - 1);
}
private:
vector<vector<int> > f; // 缓存
if (f[x][y] > 0) {
return f[x][y];
} else {
return f[x][y] = dfs(x - 1, y) + dfs(x, y - 1);
}
}
};
动规
既然可以用备忘录法自顶向下解决,也一定可以用动规自底向上解决。
f[i][j]=f[i-1][j]+f[i][j-1]
304
Unique Paths
// Unique Paths
// 动规,滚动数组
// 时间复杂度O(n^2),空间复杂度O(n)
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> f(n, 0);
f[0] = 1;
for (int i = 0; i < m; i++) {
for (int j = 1; j < n; j++) {
// 左边的f[j],表示更新后的f[j],与公式中的f[i][j]对应
// 右边的f[j],表示老的f[j],与公式中的f[i-1][j]对应
f[j] = f[j] + f[j - 1];
}
}
return f[n - 1];
}
};
数学公式
一个 m 行, n 列的矩阵,机器人从左上走到右下总共需要的步数是 m+n-2 ,其中向下走的步数是 m-
1 ,因此问题变成了在 m+n-2 个操作中,选择 m–1 个时间点向下走,选择方式有多少种。即 Cm−1
m+n−2 。
305
Unique Paths
相关题目
Unique Paths II
Minimum Path Sum
306
Unique Paths II
Unique Paths II
描述
Follow up for "Unique Paths":
Now consider if some obstacles are added to the grids. How many unique paths would there be?
For example,
[
[0,0,0],
[0,1,0],
[0,0,0]
]
备忘录法
在上一题的基础上改一下即可。相比动规,简单得多。
307
Unique Paths II
// Unique Paths II
// 深搜 + 缓存,即备忘录法
class Solution {
public:
int uniquePathsWithObstacles(const vector<vector<int> >& obstacleGrid) {
const int m = obstacleGrid.size();
const int n = obstacleGrid[0].size();
if (obstacleGrid[0][0] || obstacleGrid[m - 1][n - 1]) return 0;
// (x,y)是障碍
if (obstacleGrid[x][y]) return 0;
if (f[x][y] > 0) {
return f[x][y];
} else {
return f[x][y] = dfs(obstacleGrid, x - 1, y) +
dfs(obstacleGrid, x, y - 1);
}
}
};
动规
与上一题类似,但要特别注意第一列的障碍。在上一题中,第一列全部是1,但是在这一题中不同,第一列
如果某一行有障碍物,那么后面的行全为0。
308
Unique Paths II
// Unique Paths II
// 动规,滚动数组
// 时间复杂度O(n^2),空间复杂度O(n)
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int> > &obstacleGrid) {
const int m = obstacleGrid.size();
const int n = obstacleGrid[0].size();
if (obstacleGrid[0][0] || obstacleGrid[m-1][n-1]) return 0;
相关题目
Unique Paths
Minimum Path Sum
309
N-Queens
N-Queens
描述
The n-queens puzzle is the problem of placing n queens on an n × n chessboard such that no two
queens attack each other.
Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.'
both indicate a queen and an empty space respectively.
For example, There exist two distinct solutions to the 4-queens puzzle:
[
[".Q..", // Solution 1
"...Q",
"Q...",
"..Q."],
["..Q.", // Solution 2
"Q...",
"...Q",
".Q.."]
]
分析
经典的深搜题。
310
N-Queens
代码1
// N-Queens
// 深搜+剪枝
// 时间复杂度O(n!*n),空间复杂度O(n)
class Solution {
public:
vector<vector<string> > solveNQueens(int n) {
vector<vector<string> > result;
vector<int> C(n, -1); // C[i]表示第i行皇后所在的列编号
dfs(C, result, 0);
return result;
}
private:
void dfs(vector<int> &C, vector<vector<string> > &result, int row) {
const int N = C.size();
if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解
vector<string> solution;
for (int i = 0; i < N; ++i) {
string s(N, '.');
for (int j = 0; j < N; ++j) {
if (j == C[i]) s[j] = 'Q';
}
solution.push_back(s);
}
result.push_back(solution);
return;
}
/**
* 能否在 (row, col) 位置放一个皇后.
*
* @param C 棋局
* @param row 当前正在处理的行,前面的行都已经放了皇后了
* @param col 当前列
* @return 能否放一个皇后
*/
bool isValid(const vector<int> &C, int row, int col) {
311
N-Queens
代码2
312
N-Queens
// N-Queens
// 深搜+剪枝
// 时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
vector<vector<string> > solveNQueens(int n) {
this->columns = vector<bool>(n, false);
this->main_diag = vector<bool>(2 * n - 1, false);
this->anti_diag = vector<bool>(2 * n - 1, false);
313
N-Queens
相关题目
N-Queens II
314
N-Queens II
N-Queens II
描述
Follow up for N-Queens problem.
Now, instead outputting board configurations, return the total number of distinct solutions.
分析
只需要输出解的个数,不需要输出所有解,代码要比上一题简化很多。设一个全局计数器,每找到一个解
就增1。
代码1
315
N-Queens II
// N-Queens II
// 深搜+剪枝
// 时间复杂度O(n!*n),空间复杂度O(n)
class Solution {
public:
int totalNQueens(int n) {
this->count = 0;
316
N-Queens II
代码2
// N-Queens II
// 深搜+剪枝
// 时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
int totalNQueens(int n) {
this->count = 0;
this->columns = vector<bool>(n, false);
this->main_diag = vector<bool>(2 * n - 1, false);
this->anti_diag = vector<bool>(2 * n - 1, false);
相关题目
317
N-Queens II
N-Queens
318
Restore IP Addresses
Restore IP Addresses
描述
Given a string containing only digits, restore it by returning all possible valid IP address combinations.
分析
必须要走到底部才能判断解是否合法,深搜。
代码
319
Restore IP Addresses
// Restore IP Addresses
// 时间复杂度O(n^4),空间复杂度O(n)
class Solution {
public:
vector<string> restoreIpAddresses(const string& s) {
vector<string> result;
vector<string> ip; // 存放中间结果
dfs(s, ip, result, 0);
return result;
}
/**
* @brief 解析字符串
* @param[in] s 字符串,输入数据
* @param[out] ip 存放中间结果
* @param[out] result 存放所有可能的IP地址
* @param[in] start 当前正在处理的 index
* @return 无
*/
void dfs(string s, vector<string>& ip, vector<string> &result,
size_t start) {
if (ip.size() == 4 && start == s.size()) { // 找到一个合法解
result.push_back(ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]);
return;
}
int num = 0;
for (size_t i = start; i < start + 3 && i < s.size(); i++) {
num = num * 10 + (s[i] - '0');
320
Combination Sum
Combination Sum
描述
Given a set of candidate numbers ( C ) and a target number ( T ), find all unique combinations in C
where the candidate numbers sums to T .
The same repeated number may be chosen from C unlimited number of times.
Note:
For example, given candidate set 2,3,6,7 and target 7 , A solution set is:
[7]
[2, 2, 3]
分析
无
代码
321
Combination Sum
// Combination Sum
// 时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > combinationSum(vector<int> &nums, int target) {
sort(nums.begin(), nums.end());
vector<vector<int> > result; // 最终结果
vector<int> path; // 中间结果
dfs(nums, path, result, target, 0);
return result;
}
private:
void dfs(vector<int>& nums, vector<int>& path, vector<vector<int> > &result,
int gap, int start) {
if (gap == 0) { // 找到一个合法解
result.push_back(path);
return;
}
for (size_t i = start; i < nums.size(); i++) { // 扩展状态
if (gap < nums[i]) return; // 剪枝
path.push_back(nums[i]); // 执行扩展动作
dfs(nums, path, result, gap - nums[i], i);
path.pop_back(); // 撤销动作
}
}
};
相关题目
Combination Sum II
Combination Sum III
322
Combination Sum II
Combination Sum II
描述
Given a collection of candidate numbers ( C ) and a target number ( T ), find all unique combinations in
C where the candidate numbers sums to T .
Note:
For example, given candidate set 10,1,2,7,6,1,5 and target 8 , A solution set is:
[1, 7]
[1, 2, 5]
[2, 6]
[1, 1, 6]
分析
无
代码
323
Combination Sum II
// Combination Sum II
// 时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
vector<vector<int> > combinationSum2(vector<int> &nums, int target) {
sort(nums.begin(), nums.end()); // 跟第 50 行配合,
// 确保每个元素最多只用一次
vector<vector<int> > result;
vector<int> path;
dfs(nums, path, result, target, 0);
return result;
}
private:
// 使用nums[start, nums.size())之间的元素,能找到的所有可行解
static void dfs(const vector<int> &nums, vector<int> &path,
vector<vector<int> > &result, int gap, int start) {
if (gap == 0) { // 找到一个合法解
result.push_back(path);
return;
}
previous = nums[i];
path.push_back(nums[i]);
dfs(nums, path, result, gap - nums[i], i + 1);
path.pop_back(); // 恢复环境
}
}
};
相关题目
Combination Sum
Combination Sum III
324
Combination Sum III
描述
Find all possible combinations of k numbers that add up to a number n , given that only numbers from
1 to 9 can be used and each combination should be a unique set of numbers.
Ensure that numbers within the set are sorted in ascending order.
Example 1:
Input: k =3, n =7
Output: [[1,2,4]]
Example 2:
Input: k =3, n =9
分析
这是一个多阶段问题,目标是求所有解,显然用深搜+剪枝,即回溯法。
代码
相关题目
Combination Sum
Combination Sum II
325
Generate Parentheses
Generate Parentheses
描述
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
分析
小括号串是一个递归结构,跟单链表、二叉树等递归结构一样,首先想到用递归。
代码1
// Generate Parentheses
// 时间复杂度O(TODO),空间复杂度O(n)
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
string path;
if (n > 0) generate(n, path, result, 0, 0);
return result;
}
// l 表示 ( 出现的次数, r 表示 ) 出现的次数
void generate(int n, string& path, vector<string> &result, int l, int r) {
if (l == n) {
string s(path);
result.push_back(s.append(n - r, ')'));
return;
}
path.push_back('(');
generate(n, path, result, l + 1, r);
path.pop_back();
if (l > r) {
path.push_back(')');
generate(n, path, result, l, r + 1);
path.pop_back();
}
}
};
326
Generate Parentheses
代码2
另一种递归写法,更加简洁。
// Generate Parentheses
// @author 连城 (http://weibo.com/lianchengzju)
class Solution {
public:
vector<string> generateParenthesis (int n) {
if (n == 0) return vector<string>(1, "");
if (n == 1) return vector<string> (1, "()");
vector<string> result;
return result;
}
};
相关题目
Valid Parentheses
Longest Valid Parentheses
327
Sudoku Solver
Sudoku Solver
描述
Write a program to solve a Sudoku puzzle by filling the empty cells.
You may assume that there will be only one unique solution.
分析
无。
代码
328
Sudoku Solver
// Sudoku Solver
// 时间复杂度O(9^4),空间复杂度O(1)
class Solution {
public:
void solveSudoku(vector<vector<char> > &board) {
_solveSudoku(board);
}
private:
bool _solveSudoku(vector<vector<char> > &board) {
for (int i = 0; i < 9; ++i)
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
for (int k = 0; k < 9; ++k) {
board[i][j] = '1' + k;
if (isValid(board, i, j) && _solveSudoku(board))
return true;
board[i][j] = '.';
}
return false;
}
}
return true;
}
// 检查 (x, y) 是否合法
bool isValid(const vector<vector<char> > &board, int x, int y) {
int i, j;
for (i = 0; i < 9; i++) // 检查 y 列
if (i != x && board[i][y] == board[x][y])
return false;
for (j = 0; j < 9; j++) // 检查 x 行
if (j != y && board[x][j] == board[x][y])
return false;
for (i = 3 * (x / 3); i < 3 * (x / 3 + 1); i++)
for (j = 3 * (y / 3); j < 3 * (y / 3 + 1); j++)
if ((i != x || j != y) && board[i][j] == board[x][y])
return false;
return true;
}
};
相关题目
Valid Sudoku
329
Word Search
Word Search
描述
Given a 2D board and a word, find if the word exists in the grid.
The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are
those horizontally or vertically neighbouring. The same letter cell may not be used more than once.
[
["ABCE"],
["SFCS"],
["ADEE"]
]
分析
无。
代码
330
Word Search
// Word Search
// 深搜,递归
// 时间复杂度O(n^2*m^2),空间复杂度O(n^2)
class Solution {
public:
bool exist(const vector<vector<char> > &board, const string& word) {
const int m = board.size();
const int n = board[0].size();
vector<vector<bool> > visited(m, vector<bool>(n, false));
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
if (dfs(board, word, 0, i, j, visited))
return true;
return false;
}
private:
static bool dfs(const vector<vector<char> > &board, const string &word,
int index, int x, int y, vector<vector<bool> > &visited) {
if (index == word.size())
return true; // 收敛条件
visited[x][y] = true;
bool ret = dfs(board, word, index + 1, x - 1, y, visited) || // 上
dfs(board, word, index + 1, x + 1, y, visited) || // 下
dfs(board, word, index + 1, x, y - 1, visited) || // 左
dfs(board, word, index + 1, x, y + 1, visited); // 右
visited[x][y] = false;
return ret;
}
};
331
总结
小结
适用场景
输入数据:如果是递归数据结构,如单链表,二叉树,集合,则百分之百可以用深搜;如果是非递归数据
结构,如一维数组,二维数组,字符串,图,则概率小一些。
状态转换图:树或者DAG。
求解目标:多阶段存在性问题。必须要走到最深(例如对于树,必须要走到叶子节点)才能得到一个解,
这种情况适合用深搜。
思考的步骤
1. 是求路径条数,还是路径本身(或动作序列)?深搜最常见的三个问题,求可行解的总数,求一个可
行解,求所有可行解。
i. 如果是路径条数,则不需要存储路径。
ii. 如果是求路径本身,则要用一个数组 path[] 存储路径。跟宽搜不同,宽搜虽然最终求的也是一
条路径,但是需要存储扩展过程中的所有路径,在没找到答案之前所有路径都不能放弃;而深
搜,在搜索过程中始终只有一条路径,因此用一个数组就足够了。
2. 只要求一个解,还是要求所有解?如果只要求一个解,那找到一个就可以返回;如果要求所有解,找
到了一个后,还要继续扩展,直到遍历完。广搜一般只要求一个解,因而不需要考虑这个问题(广搜
当然也可以求所有解,这时需要扩展到所有叶子节点,相当于在内存中存储整个状态转换图,非常占
内存,因此广搜不适合解这类问题)。
3. 如何表示状态?即一个状态需要存储哪些些必要的数据,才能够完整提供如何扩展到下一步状态的所
有信息。跟广搜不同,深搜的惯用写法,不是把数据记录在状态 struct 里,而是添加函数参数(有
时为了节省递归堆栈,用全局变量), struct 里的字段与函数参数一一对应。
4. 如何扩展状态?这一步跟上一步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数
据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层
走,对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。
5. 终止条件是什么?终止条件是指到了不能扩展的末端节点。对于树,是叶子节点,对于图或隐式图,
是出度为0的节点。
6. {收敛条件是什么?收敛条件是指找到了一个合法解的时刻。如果是正向深搜(父状态处理完了才进行
递归,即父状态不依赖子状态,递归语句一定是在最后,尾递归),则是指是否达到目标状态;如果
是逆向深搜(处理父状态时需要先知道子状态的结果,此时递归语句不在最后),则是指是否到达初
始状态。
由于很多时候终止条件和收敛条件是是合二为一的,因此很多人不区分这两种条件。仔细区分这两种条
件,还是很有必要的。
为了判断是否到了收敛条件,要在函数接口里用一个参数记录当前的位置(或距离目标还有多远)。如果
是求一个解,直接返回这个解;如果是求所有解,要在这里收集解,即把第一步中表示路径的数
组 path[] 复制到解集合里。}
332
总结
1. 关于判重
i. 是否需要判重?如果状态转换图是一棵树,则不需要判重,因为在遍历过程中不可能重复;如果
状态转换图是一个DAG,则需要判重。这一点跟BFS不一样,BFS的状态转换图总是DAG,必须
要判重。
ii. 怎样判重?跟广搜相同,见第 ??? 节。同时,DAG说明存在重叠子问题,此时可以用缓存加速,
见第8步。 \end{enumerate}
2. 如何加速?
i. 剪枝。深搜一定要好好考虑怎么剪枝,成本小收益大,加几行代码,就能大大加速。这里没有通
用方法,只能具体问题具体分析,要充分观察,充分利用各种信息来剪枝,在中间节点提前返
回。
ii. 缓存。
i. 前提条件:状态转换图是一个DAG。DAG=>存在重叠子问题=>子问题的解会被重复利用,用
缓存自然会有加速效果。如果依赖关系是树状的(例如树,单链表等),没必要加缓存,因为
子问题只会一层层往下,用一次就再也不会用到,加了缓存也没什么加速效果。
ii. 具体实现:可以用数组或HashMap。维度简单的,用数组;维度复杂的,用HashMap,
C++有 map ,C++ 11以后有 unordered_map ,比 map 快。
拿到一个题目,当感觉它适合用深搜解决时,在心里面把上面8个问题默默回答一遍,代码基本上就能写出
来了。对于树,不需要回答第5和第8个问题。如果读者对上面的经验总结看不懂或感觉“不实用”,很正常,
因为这些经验总结是我做了很多题目后总结出来的,从思维的发展过程看,“经验总结”要晚于感性认识,所
以这时候建议读者先做做前面的题目,积累一定的感性认识后,再回过头来看这一节的总结,一定会有共
鸣。
代码模板
333
总结
/**
* dfs模板.
* @param[in] input 输入数据指针
* @param[out] path 当前路径,也是中间结果
* @param[out] result 存放最终结果
* @param[inout] cur or gap 标记当前位置或距离目标的距离
* @return 路径长度,如果是求路径本身,则不需要返回长度
*/
void dfs(type &input, type &path, type &result, int cur or gap) {
if (数据非法) return 0; // 终止条件
if (cur == input.size()) { // 收敛条件
// if (gap == 0) {
将path放入result
}
if (可以剪枝) return;
for(...) { // 执行所有可能的扩展动作
执行动作,修改path
dfs(input, step + 1 or gap--, result);
恢复path
}
}
深搜与回溯法的区别
深搜(Depth-first search, DFS)的定义见 http://en.wikipedia.org/wiki/Depth_first_search,回溯法
(backtracking)的定义见 http://en.wikipedia.org/wiki/Backtracking
回溯法 = 深搜 + 剪枝。一般大家用深搜时,或多或少会剪枝,因此深搜与回溯法没有什么不同,可以在它
们之间画上一个等号。本书同时使用深搜和回溯法两个术语,但读者可以认为二者等价。
深搜一般用递归(recursion)来实现,这样比较简洁。
深搜能够在候选答案生成到一半时,就进行判断,抛弃不满足要求的答案,所以深搜比暴力搜索法要快。
深搜与递归的区别
深搜经常用递归(recursion)来实现,二者常常同时出现,导致很多人误以为他俩是一个东西。
深搜,是逻辑意义上的算法,递归,是一种物理意义上的实现,它和迭代(iteration)是对应的。深搜,可以
用递归来实现,也可以用栈来实现;而递归,一般总是用来实现深搜。可以说,递归一定是深搜,深搜不
一定用递归。
递归有两种加速策略,一种是 剪枝(prunning),对中间结果进行判断,提前返回;一种是缓存,缓存中间
结果,防止重复计算,用空间换时间。
334
总结
既然递归一定是深搜,为什么很多书籍都同时使用这两个术语呢?在递归味道更浓的地方,一般用递归这
个术语,在深搜更浓的场景下,用深搜这个术语,读者心里要弄清楚他俩大部分时候是一回事。在单链
表、二叉树等递归数据结构上,递归的味道更浓,这时用递归这个术语;在图、隐式图等数据结构上,深
搜的味道更浓,这时用深搜这个术语。
335
分治法
本章主要讲分治法。
336
Pow(x,n)
Pow(x,n)
描述
Implement pow(x, n) .
分析
代码
// Pow(x, n)
// 二分法,$x^n = x^{n/2} * x^{n/2} * x^{n\%2}$
// 时间复杂度O(logn),空间复杂度O(1)
class Solution {
public:
double myPow(double x, int n) {
if (n < 0) return 1.0 / power(x, -n);
else return power(x, n);
}
private:
double power(double x, int n) {
if (n == 0) return 1;
double v = power(x, n / 2);
if (n % 2 == 0) return v * v;
else return v * v * x;
}
};
相关题目
Sqrt(x)
337
Sqrt(x)
Sqrt(x)
描述
Implement int sqrt(int x) .
分析
二分查找
代码
// LeetCode, Sqrt(x)
// 二分查找
// 时间复杂度O(logn),空间复杂度O(1)
class Solution {
public:
int mySqrt(int x) {
int left = 1, right = x / 2;
int last_mid; // 记录最近一次mid
if (x < 2) return x;
相关题目
Pow(x)
338
贪心法
本章主要讲贪心法。
339
Jump Game
Jump Game
描述
Given an array of non-negative integers, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
For example:
分析
由于每层最多可以跳 A[i] 步,也可以跳0或1步,因此如果能到达最高层,则说明每一层都可以到达。有
了这个条件,说明可以用贪心法。
思路一:正向,从0出发,一层一层网上跳,看最后能不能超过最高层,能超过,说明能到达,否则不能到
达。
思路二:逆向,从最高层下楼梯,一层一层下降,看最后能不能下降到第0层。
代码1
// Jump Game
// 思路1,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
bool canJump(const vector<int>& nums) {
int reach = 1; // 最右能跳到哪里
for (int i = 0; i < reach && reach < nums.size(); ++i)
reach = max(reach, i + 1 + nums[i]);
return reach >= nums.size();
}
};
代码2
340
Jump Game
// Jump Game
// 思路2,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
bool canJump (const vector<int>& nums) {
if (nums.empty()) return true;
// 逆向下楼梯,最左能下降到第几层
int left_most = nums.size() - 1;
return left_most == 0;
}
};
代码3
// Jump Game
// 思路三,动规,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
bool canJump(const vector<int>& nums) {
vector<int> f(nums.size(), 0);
f[0] = 0;
for (int i = 1; i < nums.size(); i++) {
f[i] = max(f[i - 1], nums[i - 1]) - 1;
if (f[i] < 0) return false;;
}
return f[nums.size() - 1] >= 0;
}
};
相关题目
Jump Game II
341
Jump Game II
Jump Game II
描述
Given an array of non-negative integers, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Your goal is to reach the last index in the minimum number of jumps.
The minimum number of jumps to reach the last index is 2. (Jump 1 step from index 0 to 1, then 3 steps
to the last index.)
分析
贪心法。
代码1
代码2
342
Jump Game II
// Jump Game II
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int jump(const vector<int>& nums) {
int result = 0;
// the maximum distance that has been reached
int last = 0;
// the maximum distance that can be reached by using "ret+1" steps
int cur = 0;
for (int i = 0; i < nums.size(); ++i) {
if (i > last) {
last = cur;
++result;
}
cur = max(cur, i + nums[i]);
}
return result;
}
};
相关题目
Jump Game
343
Best Time to Buy and Sell Stock
描述
Say you have an array for which the i-th element is the price of a given stock on day i.
If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the
stock), design an algorithm to find the maximum profit.
分析
贪心法,分别找到价格最低和最高的一天,低进高出,注意最低的一天要在最高的一天之前。
代码
相关题目
Best Time to Buy and Sell Stock II
Best Time to Buy and Sell Stock III
344
Best Time to Buy and Sell Stock II
描述
Say you have an array for which the i-th element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie,
buy one and sell one share of the stock multiple times). However, you may not engage in multiple
transactions at the same time (ie, you must sell the stock before you buy again).
分析
贪心法,低进高出,把所有正的价格差价相加起来。
代码
相关题目
Best Time to Buy and Sell Stock
Best Time to Buy and Sell Stock III
345
Longest Substring Without Repeating Characters
描述
Given a string, find the length of the longest substring without repeating characters. For example, the
longest substring without repeating letters for "abcabcbb" is "abc" , which the length is 3. For
"bbbbb" the longest substring is "b" , with the length of 1.
分析
假设子串里含有重复字符,则父串一定含有重复字符,单个子问题就可以决定父问题,因此可以用贪心
法。跟动规不同,动规里,单个子问题只能影响父问题,不足以决定父问题。
Figure: 不含重复字符的最长子串
代码
346
Longest Substring Without Repeating Characters
347
Container With Most Water
描述
Given n non-negative integers a 1, a 2, ..., an , where each represents a point at coordinate (i, ai ). n vertical
lines are drawn such that the two endpoints of line i is at (i, ai ) and (i, 0) . Find two lines, which
together with x-axis forms a container, such that the container contains the most water.
分析
每个容器的面积,取决于最短的木板。
代码
相关题目
Trapping Rain Water
Largest Rectangle in Histogram
348
Patching Array
Patching Array
描述
Given a sorted positive integer array nums and an integer n , add/patch elements to the array such
that any number in range [1, n] inclusive can be formed by the sum of some elements in the array.
Return the minimum number of patches required.
Example 1:
Combinations of nums are [1], [3], [1,3] , which form possible sums of: 1, 3, 4 .
Now if we add/patch 2 to nums, the combinations are: [1], [2], [3], [1,3], [2,3], [1,2,3] .
Example 2:
Example 3:
分析
首先可以确定的是,
349
Patching Array
参考资料:https://leetcode.com/discuss/82822/solution-explanation
代码
350
动态规划
动态规划
本章主要讲各种动态规划题。
351
Triangle
Triangle
描述
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent
numbers on the row below.
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
Note: Bonus point if you are able to do this using only O(n) extra space, where n is the total number of
rows in the triangle.
分析
设状态为 f(i, j) ,表示从从位置 (i,j) 出发,路径的最小和,则状态转移方程为
代码
// Triangle
// 时间复杂度O(n^2),空间复杂度O(1)
class Solution {
public:
int minimumTotal (vector<vector<int>>& triangle) {
for (int i = triangle.size() - 2; i >= 0; --i)
for (int j = 0; j < i + 1; ++j)
triangle[i][j] += min(triangle[i + 1][j],
triangle[i + 1][j + 1]);
352
Maximum Subarray
Maximum Subarray
描述
Find the contiguous subarray within an array (containing at least one number) which has the largest sum.
For example, given the array [−2,1,−3,4,−1,2,1,−5,4] , the contiguous subarray [4,−1,2,1] has
the largest sum = 6 .
分析
最大连续子序列和,非常经典的题。
当我们从头到尾遍历这个数组的时候,对于数组里的一个整数,它有几种选择呢?它只有两种选择: 1、
加入之前的SubArray;2. 自己另起一个SubArray。那什么时候会出现这两种情况呢?
如果之前SubArray的总体和大于0的话,我们认为其对后续结果是有贡献的。这种情况下我们选择加入之前
的SubArray
如果之前SubArray的总体和为0或者小于0的话,我们认为其对后续结果是没有贡献,甚至是有害的(小于
0时)。这种情况下我们选择以这个数字开始,另起一个SubArray。
解释如下:
情况一,S[j]不独立,与前面的某些数组成一个连续子序列,则最大连续子序列和为 f[j-1]+S[j] 。
情况二,S[j]独立划分成为一段,即连续子序列仅包含一个数S[j],则最大连续子序列和为 S[j] 。
其他思路:
思路2:直接在i到j之间暴力枚举,复杂度是 O(n^3)
思路3:处理后枚举,连续子序列的和等于两个前缀和之差,复杂度 O(n^2) 。
思路4:分治法,把序列分为两段,分别求最大连续子序列和,然后归并,复杂度 O(nlog n)
思路5:把思路2 O(n^2) 的代码稍作处理,得到 O(n) 的算法
思路6:当成M=1的最大M子段和
动规
353
Maximum Subarray
// Maximum Subarray
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int maxSubArray(const vector<int>& nums) {
int maxLocal = nums[0];
int global = nums[0];
for (int i = 1; i < nums.size(); ++i) {
maxLocal = max(nums[i], nums[i] + maxLocal);
global = max(global, maxLocal);
}
return global;
}
};
思路5
// Maximum Subarray
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
int maxSubArray(vector<int>& A) {
return mcss(A.begin(), A.end());
}
private:
// 思路5,求最大连续子序列和
template <typename Iter>
static int mcss(Iter begin, Iter end) {
int result, cur_min;
const int n = distance(begin, end);
int *sum = new int[n + 1]; // 前n项和
sum[0] = 0;
result = INT_MIN;
cur_min = sum[0];
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + *(begin + i - 1);
}
for (int i = 1; i <= n; i++) {
result = max(result, sum[i] - cur_min);
cur_min = min(cur_min, sum[i]);
}
delete[] sum;
return result;
}
};
相关题目
Maximum Subarray
354
Maximum Subarray
355
Maximum Product Subarray
描述
Find the contiguous subarray within an array (containing at least one number) which has the largest
product.
For example, given the array [2,3,-2,4] , the contiguous subarray [2,3] has the largest product =
6 .
分析
这题跟“最大连续子序列和”非常类似,只不过变成了“最大连续子序列积”,所以解决思路也很类似。
仅仅有一个小细节需要注意,就是负负得正,两个负数的乘积是正数,因此我们不仅要跟踪最大值,还要
跟踪最小值。
动规
// maximum-product-subarray
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int maxProduct(vector& nums) {
int maxLocal = nums[0];
int minLocal = nums[0];
int global = nums[0];
相关题目
Maximum Subarray
Binary Tree Maximum Path Sum
356
Longest Increasing Subsequence
描述
Given an unsorted array of integers, find the length of longest increasing subsequence.
For example,
The longest increasing subsequence is [2, 3, 7, 101] , therefore the length is 4 . Note that there
may be more than one LIS combination, it is only necessary for you to return the length.
解法1 动规
这是一个多阶段决策问题,求最长,是一个最优化问题,用 BFS, 贪心或DP。
如果用BFS,首先用数组中的所有元素作为根节点,形成n颗树,每棵树开始往下扩展,出现逆序则终止,
最后计算每棵树的高度,取最大,就是最终结果。算法复杂度为 O(n!) 。
本题中,一个节点往下扩展的时候,没有一个确定的准则,让你走哪些边,本题不具有贪心选择性质,因
此不能用贪心法。
我们来尝试用DP来解决这题。最重要的是要定义出状态。首先从状态扩展这方面看,对于数组中的一个元
素,它往后走,凡是比它大的元素,都可以作为下一步,因此这里找不到突破口。
我们换一个角度,从结果来入手,我们要求的最长递增子序列,一个递增子序列,肯定是有首尾两个端点
的,假设我们定义 f[i] 为以第 i 个元素为起点的最长递增子序列,那么 f[i] 和 f[j] 之间没有必然联
系,这个状态不好用。
有了状态和状态转移方程,代码就不难写了。
357
Longest Increasing Subsequence
维护一个单调递增序列,遍历数组,二分查找每一个数在单调序列中的位置,然后替换之。
358
Longest Increasing Subsequence
return first;
}
};
359
Palindrome Partitioning II
Palindrome Partitioning II
描述
Given a string s, partition s such that every substring of the partition is a palindrome.
Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.
分析
定义状态 f(i,j) 表示区间 [i,j] 之间最小的cut数,则状态转移方程为
这是一个二维函数,实际写代码比较麻烦。
所以要转换成一维DP。如果每次,从i往右扫描,每找到一个回文就算一次DP的话,就可以转换为 f(i)=
区间[i, n-1]之间最小的cut数 ,n为字符串长度,则状态转移方程为
代码
360
Palindrome Partitioning II
// Palindrome Partitioning II
// 时间复杂度O(n^2),空间复杂度O(n^2)
class Solution {
public:
int minCut(const string& s) {
const int n = s.size();
int f[n+1];
bool p[n][n];
fill_n(&p[0][0], n * n, false);
//the worst case is cutting by each char
for (int i = 0; i = 0; i--) {
for (int j = i; j < n; j++) {
if (s[i] == s[j] && (j - i < 2 || p[i + 1][j - 1])) {
p[i][j] = true;
f[i] = min(f[i], f[j + 1] + 1);
}
}
}
return f[0];
}
};
相关题目
Palindrome Partitioning
361
Maximal Rectangle
Maximal Rectangle
描述
Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and return its
area.
分析
无
代码
362
Maximal Rectangle
// Maximal Rectangle
// 时间复杂度O(n^2),空间复杂度O(n)
class Solution {
public:
int maximalRectangle(vector<vector<char> > &matrix) {
if (matrix.empty()) return 0;
int ret = 0;
for (int i = 0; i < m; ++i) {
int left = 0, right = n;
// calculate L(i, j) from left to right
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == '1') {
++H[j];
L[j] = max(L[j], left);
} else {
left = j+1;
H[j] = 0; L[j] = 0; R[j] = n;
}
}
// calculate R(i, j) from right to left
for (int j = n-1; j >= 0; --j) {
if (matrix[i][j] == '1') {
R[j] = min(R[j], right);
ret = max(ret, H[j]*(R[j]-L[j]));
} else {
right = j;
}
}
}
return ret;
}
};
363
Best Time to Buy and Sell Stock III
描述
Say you have an array for which the i-th element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most two transactions.
Note: You may not engage in multiple transactions at the same time (ie, you must sell the stock before
you buy again).
分析
设状态 f(i) ,表示区间[0, i](0 ≤ i ≤ n − 1)的最大利润,状态 g(i) ,表示区间[i, n − 1](0 ≤ i ≤ n − 1)的
最大利润,则最终答案为max {f (i) + g(i)} , 0 ≤ i ≤ n − 1。
允许在一天内买进又卖出,相当于不交易,因为题目的规定是最多两次,而不是一定要两次。
代码
364
Best Time to Buy and Sell Stock III
int max_profit = 0;
for (int i = 0; i < n; ++i)
max_profit = max(max_profit, f[i] + g[i]);
return max_profit;
}
};
相关题目
Best Time to Buy and Sell Stock
Best Time to Buy and Sell Stock II
365
Best Time to Buy and Sell Stock IV
描述
Say you have an array for which the i-th element is the price of a given stock on day i .
Design an algorithm to find the maximum profit. You may complete at most k transactions.
Note: You may not engage in multiple transactions at the same time (ie, you must sell the stock before
you buy again).
分析
设两个状态, global[i][j] 表示i天前最多可以进行j次交易的最大利润, local[i][j] 表示i天前最多
可以进行j次交易,且在第i天进行了第j次交易的最大利润。状态转移方程如下:
关于 global 的状态转移方程比较简单,不断地和已经计算出的local进行比较,把大的保存在global中。
关于 local 的状态转移方程,取下面二者中较大的一个:
注意,当 k 大于数组的大小时,上述算法将变得低效,此时可以改为不限交易次数的方式解决,即等价于
"Best Time to Buy and Sell Stock II"。
解法1
解法2 最长m段子段和
366
Best Time to Buy and Sell Stock with Cooldown
描述
Almost the ame as Best Time to Buy and Sell Stock II but with one restriction: after you sell your stock,
you cannot buy stock on next day. (ie, cooldown 1 day).
Example:
prices = [1, 2, 3, 0, 2]
maxProfit = 3
transactions = [buy, sell, cooldown, buy, sell]
分析
这题比Best Time to Buy and Sell Stock II多了一个cooldown的条件,就变得麻烦多了。这题是一个多阶段
优化问题,首先范围缩小到广搜,贪心或者动规。因为每步之间互相牵连,贪心显然不行。广搜固然可
以,不过是 O(2^n) 复杂度,所以我们先考虑用动规。
对于 sell[i] ,最大利润有两种可能,一是今天没动作跟昨天未持股状态一样,二是今天卖了股票,所以
状态转移方程如下:
对于 buy[i] ,最大利润有两种可能,一是今天没动作跟昨天持股状态一样,二是前天卖了股票,今天买
了股票,因为 cooldown 只能隔天交易,所以今天买股票要追溯到前天的状态。状态转移方程如下:
代码1 O(n)空间
代码2 O(1)空间
367
Interleaving String
Interleaving String
描述
Given s1, s2, s3 , find whether s3 is formed by the interleaving of s1 and s2 .
分析
设状态 f[i][j] ,表示 s1[0,i] 和 s2[0,j] ,匹配 s3[0, i+j] 。如果s1的最后一个字符等于s3的最
后一个字符,则 f[i][j]=f[i-1][j] ;如果s2的最后一个字符等于s3的最后一个字符,则 f[i][j]=f[i]
[j-1] 。因此状态转移方程如下:
递归
368
Interleaving String
// Interleaving String
// 递归,会超时,仅用来帮助理解
class Solution {
public:
bool isInterleave(const string& s1, const string& s2, const string& s3) {
if (s3.length() != s1.length() + s2.length())
return false;
template
bool isInterleave(InIt first1, InIt last1, InIt first2, InIt last2,
InIt first3, InIt last3) {
if (first3 == last3)
return first1 == last1 && first2 == last2;
动规
// Interleaving String
// 二维动规,时间复杂度O(n^2),空间复杂度O(n^2)
class Solution {
public:
bool isInterleave(const string& s1, const string& s2, const string& s3) {
if (s3.length() != s1.length() + s2.length())
return false;
vector> f(s1.length() + 1,
vector(s2.length() + 1, true));
for (size_t i = 1; i
动规+滚动数组
369
Interleaving String
// Interleaving String
// 二维动规+滚动数组,时间复杂度O(n^2),空间复杂度O(n)
class Solution {
public:
bool isInterleave(const string& s1, const string& s2, const string& s3) {
if (s1.length() + s2.length() != s3.length())
return false;
for (size_t i = 1; i
370
Scramble String
Scramble String
描述
Given a string s1 , we may represent it as a binary tree by partitioning it to two non-empty substrings
recursively.
great
/ \
gr eat
/ \ / \
g r e at
/ \
a t
To scramble the string, we may choose any non-leaf node and swap its two children.
For example, if we choose the node "gr" and swap its two children, it produces a scrambled string
"rgeat" .
rgeat
/ \
rg eat
/ \ / \
r g e at
/ \
a t
Similarly, if we continue to swap the children of nodes "eat" and "at" , it produces a scrambled string
"rgtae" .
rgtae
/ \
rg tae
/ \ / \
r g ta e
/ \
t a
Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1 .
分析
371
Scramble String
首先想到的是递归(即深搜),对两个string进行分割,然后比较四对字符串。代码虽然简单,但是复杂度
比较高。有两种加速策略,一种是剪枝,提前返回;一种是加缓存,缓存中间结果,即memorization(翻
译为记忆化搜索)。
剪枝可以五花八门,要充分观察,充分利用信息,找到能让节点提前返回的条件。例如,判断两个字符串
是否互为scamble,至少要求每个字符在两个字符串中出现的次数要相等,如果不相等则返回false。
递归
// Scramble String
// 递归,会超时,仅用来帮助理解
// 时间复杂度O(n^6),空间复杂度O(1)
class Solution {
public:
bool isScramble(const string& s1, const string& s2) {
return isScramble(s1.begin(), s1.end(), s2.begin());
}
private:
typedef string::iterator Iterator;
bool isScramble(Iterator first1, Iterator last1, Iterator first2) {
auto length = distance(first1, last1);
auto last2 = next(first2, length);
return false;
}
};
动规
372
Scramble String
// Scramble String
// 动规,时间复杂度O(n^3),空间复杂度O(n^3)
class Solution {
public:
bool isScramble(const string& s1, const string& s2) {
const int N = s1.size();
if (N != s2.size()) return false;
// f[n][i][j],表示长度为n,起点为s1[i]和
// 起点为s2[j]两个字符串是否互为scramble
bool f[N + 1][N][N];
fill_n(&f[0][0][0], (N + 1) * N * N, false);
for (int n = 1; n
递归+剪枝
373
Scramble String
// Scramble String
// 递归+剪枝
// 时间复杂度O(n^6),空间复杂度O(1)
class Solution {
public:
bool isScramble(const string& s1, const string& s2) {
return isScramble(s1.begin(), s1.end(), s2.begin());
}
private:
typedef string::const_iterator Iterator;
bool isScramble(Iterator first1, Iterator last1, Iterator first2) {
auto length = distance(first1, last1);
auto last2 = next(first2, length);
if (length == 1) return *first1 == *first2;
// 剪枝,提前返回
int A[26]; // 每个字符的计数器
fill(A, A + 26, 0);
for(int i = 0; i < length; i++) A[*(first1+i)-'a']++;
for(int i = 0; i < length; i++) A[*(first2+i)-'a']--;
for(int i = 0; i < 26; i++) if (A[i] != 0) return false;
return false;
}
};
备忘录法
374
Scramble String
// Scramble String
// 递归+unordered_map做cache,比map快
// 时间复杂度O(n^3),空间复杂度O(n^3)
class Solution {
public:
unordered_map cache;
if (length == 1)
return *first1 == *first2;
return false;
}
375
Minimum Path Sum
描述
Given a m × n grid filled with non-negative numbers, find a path from top left to bottom right which
minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time
分析
跟第 ??? 节 Unique Paths 很类似。
f[i][j]=min(f[i-1][j], f[i][j-1])+grid[i][j]
备忘录法
376
Minimum Path Sum
动规
int f[m][n];
f[0][0] = grid[0][0];
for (int i = 1; i < m; i++) {
f[i][0] = f[i - 1][0] + grid[i][0];
}
for (int i = 1; i < n; i++) {
f[0][i] = f[0][i - 1] + grid[0][i];
}
动规+滚动数组
377
Minimum Path Sum
int f[n];
fill(f, f+n, INT_MAX); // 初始值是 INT_MAX,因为后面用了min函数。
f[0] = 0;
相关题目
Unique Paths
Unique Paths II
378
Edit Distance
Edit Distance
描述
Given two words word1 and word2 , find the minimum number of steps required to convert word1 to
word2 . (each operation is counted as 1 step.)
Insert a character
Delete a character
Replace a character
分析
设状态为 f[i][j] ,表示 A[0,i] 和 B[0,j] 之间的最小编辑距离。设 A[0,i] 的形式
是 str1c , B[0,j] 的形式是 str2d ,
1. 如果 c==d ,则 f[i][j]=f[i-1][j-1] ;
2. 如果 c!=d ,
i. 如果将c替换成d,则 f[i][j]=f[i-1][j-1]+1 ;
ii. 如果在c后面添加一个d,则 f[i][j]=f[i][j-1]+1 ;
iii. 如果将c删除,则 f[i][j]=f[i-1][j]+1 ;
动规
379
Edit Distance
// Edit Distance
// 二维动规,时间复杂度O(n*m),空间复杂度O(n*m)
class Solution {
public:
int minDistance(const string &word1, const string &word2) {
const size_t n = word1.size();
const size_t m = word2.size();
// 长度为n的字符串,有n+1个隔板
int f[n + 1][m + 1];
for (size_t i = 0; i <= n; i++)
f[i][0] = i;
for (size_t j = 0; j <= m; j++)
f[0][j] = j;
动规+滚动数组
380
Edit Distance
// Edit Distance
// 二维动规+滚动数组
// 时间复杂度O(n*m),空间复杂度O(n)
class Solution {
public:
int minDistance(const string &word1, const string &word2) {
if (word1.length() < word2.length())
return minDistance(word2, word1);
upper_left = upper;
}
}
return f[word2.length()];
}
};
381
Decode Ways
Decode Ways
描述
A message containing letters from A-Z is being encoded to numbers using the following mapping:
'A' -> 1
'B' -> 2
...
'Z' -> 26
Given an encoded message containing digits, determine the total number of ways to decode it.
For example, Given encoded message "12" , it could be decoded as "AB" (1 2) or "L" (12).
分析
跟第???节Climbing Stairs很类似,不过多加一些判断逻辑。
代码
// Decode Ways
// 动规,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int numDecodings(const string &s) {
if (s.empty() || s[0] == '0') return 0;
int prev = 0;
int cur = 1;
// 长度为n的字符串,有 n+1个阶梯
for (size_t i = 1; i <= s.size(); ++i) {
if (s[i-1] == '0') cur = 0;
382
Decode Ways
相关题目
Climbing Stairs
383
Distinct Subsequences
Distinct Subsequences
描述
Given a string S and a string T , count the number of distinct subsequences of T in S .
A subsequence of a string is a new string which is formed from the original string by deleting some (can
be none) of the characters without disturbing the relative positions of the remaining characters. (ie,
"ACE" is a subsequence of "ABCDE" while "AEC" is not).
Return 3.
分析
设状态为 f(i,j) ,表示 T[0,j] 在 S[0,i] 里出现的次数。首先,无论 S[i] 和 T[j] 是否相等,若不
使用 S[i] ,则 f(i,j)=f(i-1,j) ;若 S[i]==T[j] ,则可以使用 S[i] ,此时 f(i,j)=f(i-
1,j)+f(i-1, j-1) 。
代码
// Distinct Subsequences
// 二维动规+滚动数组
// 时间复杂度O(m*n),空间复杂度O(n)
class Solution {
public:
int numDistinct(const string &S, const string &T) {
vector<int> f(T.size() + 1);
f[0] = 1;
for (int i = 0; i < S.size(); ++i) {
for (int j = T.size() - 1; j >= 0; --j) {
f[j + 1] += S[i] == T[j] ? f[j] : 0;
}
}
return f[T.size()];
}
};
384
Word Break
Word Break
描述
Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated
sequence of one or more dictionary words.
s = "leetcode" ,
分析
设状态为 f(i) ,表示 s[0,i) 是否可以分词,则状态转移方程为
深搜
// Word Break
// 深搜,超时
// 时间复杂度O(2^n),空间复杂度O(n)
class Solution {
public:
bool wordBreak(string s, unordered_set<string> &dict) {
return dfs(s, dict, 0, 1);
}
private:
static bool dfs(const string &s, unordered_set<string> &dict,
size_t start, size_t cur) {
if (cur == s.size()) {
return dict.find(s.substr(start, cur-start)) != dict.end();
}
if (dfs(s, dict, start, cur+1)) return true; // no cut
if (dict.find(s.substr(start, cur-start)) != dict.end()) // cut here
if (dfs(s, dict, cur+1, cur+1)) return true;
return false;
}
};
动规
385
Word Break
// Word Break
// 动规,时间复杂度O(n^2),空间复杂度O(n)
class Solution {
public:
bool wordBreak(string s, unordered_set<string> &dict) {
// 长度为n的字符串有n+1个隔板
vector<bool> f(s.size() + 1, false);
f[0] = true; // 空字符串
for (int i = 1; i <= s.size(); ++i) {
for (int j = i - 1; j >= 0; --j) {
if (f[j] && dict.find(s.substr(j, i - j)) != dict.end()) {
f[i] = true;
break;
}
}
}
return f[s.size()];
}
};
相关题目
Word Break II
386
Word Break II
Word Break II
描述
Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word
is a valid dictionary word.
s = "catsanddog" ,
分析
在上一题的基础上,要返回解本身。
代码
387
Word Break II
// Word Break II
// 动规,时间复杂度O(n^2),空间复杂度O(n^2)
class Solution {
public:
vector<string> wordBreak(string s, unordered_set<string> &dict) {
// 长度为n的字符串有n+1个隔板
vector<bool> f(s.length() + 1, false);
// prev[i][j]为true,表示s[j, i)是一个合法单词,可以从j处切开
// 第一行未用
vector<vector<bool> > prev(s.length() + 1, vector<bool>(s.length()));
f[0] = true;
for (size_t i = 1; i <= s.length(); ++i) {
for (int j = i - 1; j >= 0; --j) {
if (f[j] && dict.find(s.substr(j, i - j)) != dict.end()) {
f[i] = true;
prev[i][j] = true;
}
}
}
vector<string> result;
vector<string> path;
gen_path(s, prev, s.length(), path, result);
return result;
}
private:
// DFS遍历树,生成路径
void gen_path(const string &s, const vector<vector<bool> > &prev,
int cur, vector<string> &path, vector<string> &result) {
if (cur == 0) {
string tmp;
for (auto iter = path.crbegin(); iter != path.crend(); ++iter)
tmp += *iter + " ";
tmp.erase(tmp.end() - 1);
result.push_back(tmp);
}
for (size_t i = 0; i < s.size(); ++i) {
if (prev[cur][i]) {
path.push_back(s.substr(i, cur - i));
gen_path(s, prev, i, path, result);
path.pop_back();
}
}
}
};
相关题目
Word Break
388
Word Break II
389
Dungeon Game
Dungeon Game
The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon.
The dungeon consists of M x N rooms laid out in a 2D grid. Our valiant knight (K) was initially positioned
in the top-left room and must fight his way through the dungeon to rescue the princess.
The knight has an initial health point represented by a positive integer. If at any point his health point
drops to 0 or below, he dies immediately.
Some of the rooms are guarded by demons, so the knight loses health (negative integers) upon entering
these rooms; other rooms are either empty (0's) or contain magic orbs that increase the knight's health
(positive integers).
In order to reach the princess as quickly as possible, the knight decides to move only rightward or
downward in each step.
Write a function to determine the knight's minimum initial health so that he is able to rescue the
princess.
For example, given the dungeon below, the initial health of the knight must be at least 7 if he follows the
optimal path RIGHT-> RIGHT -> DOWN -> DOWN .
+--------+----+--------+
| -2 (K) | -3 | 3 |
+--------+----+--------+
| -5 | -10| 1 |
+--------+----+--------+
| 10 | 30 | -5 (P) |
+--------+----+--------+
Notes:
分析
这是一个多阶段优化问题,有广搜,贪心,动规。这题显然贪心不行,于是范围缩小到广搜和动规。本题
求最小健康点数,跟路径长度无关,因此广搜不适合。最后只剩下了动规这个方向。
接下来寻找状态转移方程。从初始值我们可以推测出状态转移是从右下角反向的,可以得到状态转移方程
如下:
390
Dungeon Game
f[i][j]=max(1, -dungeon[i][j]+min(f[i+1][j],f[i][j+1])
代码
391
House Robber
House Robber
描述
You are a professional robber planning to rob houses along a street. Each house has a certain amount of
money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have
security system connected and it will automatically contact the police if two adjacent houses were
broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the
maximum amount of money you can rob tonight without alerting the police.
分析
这是一个多阶段最优化问题,且要走到最底部才能知道答案,因此广搜排除,只剩下贪心和动规。贪心明
显要排除,只剩下动规。
解法1
解法2
在状态转移方程中,我们可以发现 f[i] 仅仅依赖前两项,因此用两个整数变量即可代替一位数组,将空
间复杂度降为 O(1) 。
392
House Robber II
House Robber II
描述
This time, all houses at this place are arranged in a circle.
分析
如果抢劫第一家,则不可以抢最后一家;否则,可以抢最后一家。因此,这个问题就转化成为了两趟动
规,可以复用 "House Robber" 的代码。
代码
393
House Robber III
描述
All houses in this place forms a binary tree. It will automatically contact the police if two directly-linked
houses were broken into on the same night.
Example 1:
3
/ \
2 3
\ \
3 1
Example 2:
3
/ \
4 5
/ \ \
1 3 1
分析
树形动规。设状态 f(root) 表示抢劫root为根节点的二叉树,root可抢也可能不抢,能得到的最大金
钱, g(root) 表示抢劫root为根节点的二叉树,但不抢root,能得到的最大金钱,则状态转移方程为
代码
394
Range Sum Query - Immutable
描述
Given an integer array nums , find the sum of the elements between indices i and j ( i ≤ j ),
inclusive.
Example:
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
Note:
分析
令状态 f[i] 为0到 i 元素之间的和,则状态转移方程为 f[i] = f[i-1] + nums[i] 。 f[i] 本质上是
累加和,有了 f[i] ,则范围[i,j]之间的和等于 f[j] - f[i-1] 。
代码
相关题目
Range Sum Query 2D - Immutable
Range Sum Query - Mmutable
395
Range Sum Query 2D - Immutable
描述
Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner
(row1, col1) and lower right corner (row2, col2) .
Figure: The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4,
3), which contains sum = 8.
Example:
Given matrix =
[
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12
Note:
分析
思路跟一维的类似,建立一个累加和矩阵。令状态 f[i][j] 表示从(0,0)到(i,j)的子矩阵的和,则状态转移
方程为
396
Range Sum Query 2D - Immutable
有了 f[i][j] , 则
代码
相关题目
Range Sum Query - Immutable
Range Sum Query - Mmutable
397
图
图
无向图的节点定义如下:
// 无向图的节点
struct UndirectedGraphNode {
int label;
vector<UndirectedGraphNode *> neighbors;
UndirectedGraphNode(int x) : label(x) {};
};
398
Clone Graph
Clone Graph
描述
Clone an undirected graph. Each node in the graph contains a label and a list of its neighbours .
We use # as a separator for each node, and , as a separator for node label and each neighbour of
the node. As an example, consider the serialized graph {0,1,2#1,2#2,2} .
The graph has a total of three nodes, and therefore contains three parts as separated by # .
1
/ \
/ \
0 --- 2
/ \
\_/
分析
广度优先遍历或深度优先遍历都可以。
DFS
399
Clone Graph
// Clone Graph
// DFS,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
UndirectedGraphNode *cloneGraph(const UndirectedGraphNode *node) {
if(node == nullptr) return nullptr;
// key is original node,value is copied node
unordered_map<const UndirectedGraphNode *,
UndirectedGraphNode *> visited;
clone(node, visited);
return visited[node];
}
private:
// DFS
static UndirectedGraphNode* clone(const UndirectedGraphNode *node,
unordered_map<const UndirectedGraphNode *,
UndirectedGraphNode *> &visited) {
// a copy already exists
if (visited.find(node) != visited.end()) return visited[node];
BFS
400
Clone Graph
// Clone Graph
// BFS,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
UndirectedGraphNode *cloneGraph(const UndirectedGraphNode *node) {
if (node == nullptr) return nullptr;
// key is original node,value is copied node
unordered_map<const UndirectedGraphNode *,
UndirectedGraphNode *> copied;
// each node in queue is already copied itself
// but neighbors are not copied yet
queue<const UndirectedGraphNode *> q;
q.push(node);
copied[node] = new UndirectedGraphNode(node->label);
while (!q.empty()) {
const UndirectedGraphNode *cur = q.front();
q.pop();
for (auto nbr : cur->neighbors) {
// a copy already exists
if (copied.find(nbr) != copied.end()) {
copied[cur]->neighbors.push_back(copied[nbr]);
} else {
UndirectedGraphNode *new_node =
new UndirectedGraphNode(nbr->label);
copied[nbr] = new_node;
copied[cur]->neighbors.push_back(new_node);
q.push(nbr);
}
}
}
return copied[node];
}
};
401
位操作
本章主要讲位操作相关的题目。
402
Reverse Bits
Reverse Bits
描述
Reverse bits of a given 32 bits unsigned integer.
Follow up: If this function is called many times, how would you optimize it?
分析
最简单直接的做法,从右向左把一位位取出来,添加到新生成的整数的最低位即可。
第二个简单的方法,左右不断交换位,直到相遇。
解法1
解法2
403
Repeated DNA Sequences
描述
All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example:
"ACGAATTCCG". When studying DNA, it is sometimes useful to identify repeated sequences within the
DNA.
Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA
molecule.
For example,
Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT" ,
Return:
["AAAAACCCCC", "CCCCCAAAAA"] .
分析
首先能想到一个简单直接的方法,用一个长度为10的窗口,从左到右扫描,放入 HashMap,并把计数器增
一。最后,把 HashMap 中所有计数器大于1的字符串输出来。时间复杂度 O(n) , 由于HashMap中存储了
所有长度为10的子串,所以空间复杂度 O(10n) 。
由于字符串中只存在 A, C, G, T 四种字符,我们可以把每个字符映射为2个bit:
A -> 00
C -> 01
G -> 10
T -> 11
解法1 简单粗暴
解法2 完美哈希
404
Number of 1 Bits
Number of 1 Bits
描述
Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also known as the
Hamming weight).
分析
最直接的方法,做32次右移,统计出1的个数。
解法1
解法2
405
Gray Code
Gray Code
描述
The gray code is a binary numeral system where two successive values differ in only one bit.
Given a non-negative integer n representing the total number of bits in the code, print the sequence of
gray code. A gray code sequence must begin with 0.
For example, given n = 2 , return [0,1,3,2] . Its gray code sequence is:
00 - 0
01 - 1
11 - 3
10 - 2
Note:
分析
格雷码(Gray Code)的定义请参考 http://en.wikipedia.org/wiki/Gray_code
自然二进制码转换为格雷码:g0 = b0 , gi = bi ⊕ bi−1
保留自然二进制码的最高位作为格雷码的最高位,格雷码次高位为二进制码的高位与次高位异或,其余各
位与次高位的求法类似。例如,将自然二进制码1001,转换为格雷码的过程是:保留最高位;然后将第1位
的1和第2位的0异或,得到1,作为格雷码的第2位;将第2位的0和第3位的0异或,得到0,作为格雷码的第
3位;将第3位的0和第4位的1异或,得到1,作为格雷码的第4位,最终,格雷码为1101。
格雷码转换为自然二进制码:b0 = g0 , b i = gi ⊕ bi−1
保留格雷码的最高位作为自然二进制码的最高位,次高位为自然二进制高位与格雷码次高位异或,其余各
位与次高位的求法类似。例如,将格雷码1000转换为自然二进制码的过程是:保留最高位1,作为自然二进
制码的最高位;然后将自然二进制码的第1位1和格雷码的第2位0异或,得到1,作为自然二进制码的第2
位;将自然二进制码的第2位1和格雷码的第3位0异或,得到1,作为自然二进制码的第3位;将自然二进制
码的第3位1和格雷码的第4位0异或,得到1,作为自然二进制码的第4位,最终,自然二进制码为1111。
这题要求生成 n 比特的所有格雷码。
方法1,最简单的方法,利用数学公式,对从 0 ∼ 2n − 1的所有整数,转化为格雷码。
406
Gray Code
数学公式
// Gray Code
// 数学公式,时间复杂度O(2^n),空间复杂度O(1)
class Solution {
public:
vector<int> grayCode(int n) {
const int size = 1 << n; // 2^n
vector<int> result;
result.reserve(size);
Reflect-and-prefix method
407
Gray Code
// Gray Code
// reflect-and-prefix method
// 时间复杂度O(2^n),空间复杂度O(1)
class Solution {
public:
vector<int> grayCode(int n) {
const int size = 1 << n;
vector<int> result;
result.reserve(size);
result.push_back(0);
for (int i = 0; i < n; i++) {
const int highest_bit = 1 << i;
for (int j = result.size() - 1; j >= 0; j--) // 要反着遍历,才能对称
result.push_back(highest_bit | result[j]);
}
return result;
}
};
408
Single Number
Single Number
描述
Given an array of integers, every element appears twice except for one. Find that single one.
Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra
memory?
分析
异或,不仅能处理两次的情况,只要出现偶数次,都可以清零。
代码
// Single Number
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int x = 0;
for (int i : nums) {
x ^= i;
}
return x;
}
};
相关题目
Single Number II
409
Single Number II
Single Number II
描述
Given an array of integers, every element appears three times except for one. Find that single one.
Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra
memory?
分析
本题和上一题 Single Number,考察的是位运算。
代码1
// Single Number II
// 方法1,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int singleNumber(vector<int>& nums) {
const int W = sizeof(int) * 8; // 一个整数的bit数,即整数字长
int count[W]; // count[i]表示在在i位出现的1的次数
fill_n(&count[0], W, 0);
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j < W; j++) {
count[j] += (nums[i] >> j) & 1;
count[j] %= 3;
}
}
int result = 0;
for (int i = 0; i < W; i++) {
result += (count[i] << i);
}
return result;
}
};
代码2
410
Single Number II
// Single Number II
// 方法2,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int one = 0, two = 0, three = 0;
for (int i : nums) {
two |= (one & i);
one ^= i;
three = ~(one & two);
one &= three;
two &= three;
}
return one;
}
};
相关题目
Single Number
411
Single Number III
描述
Given an array of numbers nums, in which exactly two elements appear only once and all the other
elements appear exactly twice. Find the two elements that appear only once.
For example:
Note:
1. The order of the result is not important. So in the above example, [5, 3] is also correct.
2. Your algorithm should run in linear runtime complexity. Could you implement it using only constant
space complexity?
分析
本题是有两个未知数,是 "Single Number" 这道题的扩展,直接做异或肯定是不行的。有没有办法把两个
未知数分开,使得可以应用Single Number 的解法呢?
设 x , y 是那两个未知数,那么如果对这个数组做异或的话,结果实质上等于 x ^ y ,因为其他数都出
现了两次,被抵消了。
代码
412
Power of Two
Power of Two
描述
Given an integer, write a function to determine if it is a power of two.
分析
如果是2的幂,则二进制的所有位中,有且仅有一个1。
还有更巧妙的办法。如果一个数是2的幂,则它的二进制最高位必然为1,其余为0,此时如果我们减1的
话,最高位降为0,其余位变为1,如果把两个数按位与,结果必然为0。
代码
413
Missing Number
Missing Number
描述
Given an array containing n distinct numbers taken from 0, 1, 2, ..., n , find the one that is
missing from the array.
For example,
Note:
Your algorithm should run in linear runtime complexity. Could you implement it using only constant extra
space complexity?
分析
本题的意思是,从1到n的整数,其中某个数丢失了,替代它的是0。要我们找出这个丢失的数。
方法1,我们可以用公式计算出从1到n的和,减去实际数组的总和,差值就是那个丢失的数。
方法2,利用异或位运算,把数组中的每一个数,与1到n进行按位异或,最后剩下的,就是丢失的数。
解法1
解法2
解法3
414
Maximum Product of Word Lengths
描述
Given a string array words , find the maximum value of length(word[i]) * length(word[j]) where
the two words do not share common letters. You may assume that each word will contain only lower case
letters. If no such two words exist, return 0.
Example 1:
Return 16
Example 2:
Return 4
Example 3:
Return 0
分析
由于只有26个小写字母,所以,我们可以为数组中的每个 word 开辟一个长为26的布尔数组作为哈希表,
然后用一个两重for循环,两两比较,如果不存在公共的字母,则计算二者的长度的乘积,取最大作为最终
结果。时间复杂度 O(26n^2) ,空间复杂度 O(26n) 。
上面的方法可以进一步优化,即长度为26的布尔数组,小于32位,可以编码为一个整数,这样两个整数按
位与,如果结果为1,说明存在公共字母,如果结果为0,说明不存在公共字母。时间复杂度 O(n^2) ,空
间复杂度 O(n) 。
解法1
解法2
415
Bitwise AND of Numbers Range
描述
Given a range [m, n] where 0 <= m <= n <= 2147483647 , return the bitwise AND of all numbers in
this range, inclusive.
分析
最简单的解法,遍历所有数,不断进行按位与,时间复杂度 O(n) 。这个做法太慢,不可接受。
101
110
111
三个数按位与后结果为0,仔细观察这个过程我们可以得出,最后的结果是该范围内所有数的左边的共同部
分,即公共左边首部(left header)。
11010
11011
11100
11101
11110
发现了规律后,这题就简单了,我们只需要写代码找到公共左边首部即可。
解法1
解法2
416
Power of Three
Power of Three
描述
Given an integer, write a function to determine if it is a power of three.
Follow up:
分析
最简单的方法,不断除以3,看最后能否得到1。
如果不用循环和递归,那么就需要找数学方法了。最简单的,我们可以把该整数对3取对数,如果结果是整
数,说明该整数是3的幂。
代码
417
Rectangle Area
Rectangle Area
描述
Find the total area covered by two rectilinear rectangles in a 2D plane.
Each rectangle is defined by its bottom left corner and top right corner as shown in the figure.
Assume that the total area is never beyond the maximum possible value of int.
分析
简单平面几何。根据容斥原理:S(M ∪ N) = S(M) + S(N) - S(M ∩ N),最关键的是求出相交部分的面积。
代码
418
数论
本节主要讲数论相关的题目。一般数论题不太适合用于面试中,所以面试中很少出现数论题,不过掌握一
些常见的数论知识还是有好处的。
419
Happy Number
Happy Number
描述
Write a function to determine if a number is "happy number".
A happy number is a number defined by the following process: Starting with any positive integer, replace
the number by the sum of the squares of its digits, and repeat the process until the number equals 1
(where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this
process ends in 1 are happy numbers.
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
分析
这题找到规律后就简单了。如果右边的出现了某个重复的数,但不是1,说明会无限循环下去,这个数就不
是快乐数,如果是1,则是快乐数。
代码
420
Ugly Number
Ugly Number
描述
Write a function to check whether a given number is an ugly number.
Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 6, 8 are ugly
while 14 is not ugly since it includes another prime factor 7.
分析
思路很简单,把 n 里面的2,3,5 全部消掉,看最后能不能剩下1。
代码
相关题目
Ugly Number II
421
Ugly Number II
Ugly Number II
描述
Write a function to find the n -th ugly number.
Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6,
8, 9, 10, 12 is the sequence of the first 10 ugly numbers.
Hint:
1. The naive approach is to call isUgly() for every number until you reach the n -th one. Most
numbers are not ugly. Try to focus your effort on generating only the ugly ones.
2. An ugly number must be multiplied by either 2, 3, or 5 from a smaller ugly number.
3. The key is how to maintain the order of the ugly numbers. Try a similar approach of merging from
three sorted lists: L 1 , L2 , and L 3 .
4. Assume you have U k , the k th ugly number. Then Uk+1 must be M in(L1 ∗ 2, L2 ∗ 3, L3 ∗ 5).
分析
根据提示中的信息,我们知道丑陋序列可以拆分成3个子序列:
每次从三个列表中取出当前最小的那个加入序列,直到第 n 个为止。
代码
相关题目
Ugly Number
Super Ugly Number
422
Super Ugly Number
描述
Write a function to find the n -th super ugly number.
Super ugly numbers are positive numbers whose all prime factors are in the given prime list primes of
size k . For example, [1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32] is the sequence of the first 12
super ugly numbers given primes = [2, 7, 13, 19] of size 4.
Note:
分析
这题是 Ugly Number II 的扩展。在"Ugly Number II"中, primes=[2,3,5] ,这题中 primes 可以自由变
化。
代码
相关题目
Ugly Number II
423
Fraction to Recurring Decimal
描述
Given two integers representing the numerator and denominator of a fraction, return the fraction in string
format.
For example,
分析
这题的难点是如何找到无限循环的那一段。仔细回想一下人脑进行除法的过程,会发现,当一个余数第二
次重复出现时,就说明小数点后开始无限循环了。
代码
424
Factorial Trailing Zeroes
描述
Given an integer n , return the number of trailing zeroes in n! .
分析
解法1
解法2
上面的解法会超时,可以优化一下。
425
Nim Game
Nim Game
描述
You are playing the following Nim Game with your friend: There is a heap of stones on the table, each
time one of you take turns to remove 1 to 3 stones. The one who removes the last stone will be the
winner. You will take the first turn to remove the stones.
Both of you are very clever and have optimal strategies for the game. Write a function to determine
whether you can win the game given the number of stones in the heap.
For example, if there are 4 stones in the heap, then you will never win the game: no matter 1, 2, or 3
stones you remove, the last stone will always be removed by your friend.
分析
这题是尼姆游戏的简化版。
尼姆游戏最流行的版本是用12枚硬币。
游戏规则很简单,游戏双方轮流取 1 枚或多枚硬币(只能在同一行),谁拿到最后一枚就算赢。
有趣的是,有人发现,当扩展到任意多行,每行有任意枚硬币时,利用二进制,可以把这个游戏玩得风生
水起。哈佛大学的数学教授布顿在 1901 年首次发表了论文详述了这个问题,也正是他,正式将这个游戏命
名为尼姆游戏。
把玩家每一步操作之后的游戏局面叫做“棋局”。在布顿的论文中,如果玩家每一步操作后的棋局能保证自己
获胜,那就是“安全的”,否则就是“不安全的”。每个不安全棋局都可以一步正确的操作变成安全的,而如果
没有正确地操作,一个安全的棋局就会变成不安全的。
回到我们上面说的那个流行版本上,可以看到在初始状态,它的二进制表示如下图:
426
Nim Game
尼姆游戏深受数学家喜爱并被广泛研究,它因此产生了很多变体。1910 年美国数学家穆尔就提出了一个,
它规则与尼姆游戏相同,只不过玩家可以从不超过指定数 k 的任意多行里拿掉硬币。有趣的是,它同样可
以通过二进制来分析,只要把安全棋局定义为:二进制表里的每列之和都可以被 k + 1 整除就可以了。
这题是行数为1,k=3的简化版尼姆游戏。由于是先手,只需要判断当前的石头数能否被4整除,如果能整
除,则一定会输,否则一定能赢。
参考资料: http://www.guokr.com/article/68595/
代码
427
模拟
模拟
这类题目思路比较简单直白,按照问题的描述,把解题的步骤一步一步直白的翻译成代码,就行了。这类
题目主要考察写代码是否熟练,是否具备基本的调试能力,编程风格是否良好等。
428
Reverse Integer
Reverse Integer
描述
Reverse digits of an integer.
Here are some good questions to ask before coding. Bonus points for you if you have already thought
through this!
If the integer's last digit is 0, what should the output be? ie, cases such as 10, 100.
Did you notice that the reversed integer might overflow? Assume the input is a 32-bit integer, then the
reverse of 1000000003 overflows. How should you handle such cases?
Throw an exception? Good, but what if throwing an exception is not an option? You would then have to
re-design the function (ie, add an extra parameter).
分析
短小精悍的题,代码也可以写的很短小。
代码
429
Reverse Integer
// Reverse Integer
// 时间复杂度O(logn),空间复杂度O(1)
// 考虑 1.负数的情况 2. 溢出的情况(正溢出&&负溢出,比如 x = -2147483648(即-2^31) )
class Solution {
public:
int reverse (int x) {
long long r = 0;
long long t = x;
t = t > 0 ? t : -t;
for (; t; t /= 10)
r = r * 10 + t % 10;
相关题目
Palindrome Number
430
Palindrome Number
Palindrome Number
描述
Determine whether an integer is a palindrome. Do this without extra space.
Some hints:
If you are thinking of converting the integer to string, note the restriction of using extra space.
You could also try reversing an integer. However, if you have solved the problem "Reverse Integer", you
know that the reversed integer might overflow. How would you handle such case?
分析
首先想到,可以利用上一题,将整数反转,然后与原来的整数比较,是否相等,相等则为 Palindrome 的。
可是 reverse()会溢出。
正确的解法是,不断地取第一位和最后一位(10进制下)进行比较,相等则取第二位和倒数第二位,直到
完成比较或者中途找到了不一致的位。
代码
// Palindrome Number
// 时间复杂度O(1),空间复杂度O(1)
class Solution {
public:
bool isPalindrome(int x) {
if (x < 0) return false;
int d = 1; // divisor
while (x / d >= 10) d *= 10;
while (x > 0) {
int q = x / d; // quotient
int r = x % 10; // remainder
if (q != r) return false;
x = x % d / 10;
d /= 100;
}
return true;
}
};
相关题目
431
Palindrome Number
Reverse Integer
Valid Palindrome
432
Insert Interval
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.
分析
无
代码
// Insert Interval
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
vector<Interval> insert(vector<Interval> &intervals, Interval newInterval) {
vector<Interval>::iterator it = intervals.begin();
while (it != intervals.end()) {
if (newInterval.end < it->start) {
intervals.insert(it, newInterval);
return intervals;
} else if (newInterval.start > it->end) {
it++;
continue;
} else {
newInterval.start = min(newInterval.start, it->start);
newInterval.end = max(newInterval.end, it->end);
it = intervals.erase(it);
}
}
intervals.insert(intervals.end(), newInterval);
return intervals;
}
};
相关题目
Merge Intervals
433
Insert Interval
434
Merge Intervals
Merge Intervals
描述
Given a collection of intervals, merge all overlapping intervals.
分析
复用一下Insert Intervals的解法即可,创建一个新的interval集合,然后每次从旧的里面取一个interval出
来,然后插入到新的集合中。
代码
// Merge Interval
//复用一下Insert Intervals的解法即可
// 时间复杂度O(n1+n2+...),空间复杂度O(1)
class Solution {
public:
vector<Interval> merge(vector<Interval> &intervals) {
vector<Interval> result;
for (int i = 0; i < intervals.size(); i++) {
insert(result, intervals[i]);
}
return result;
}
private:
vector<Interval> insert(vector<Interval> &intervals, Interval newInterval) {
vector<Interval>::iterator it = intervals.begin();
while (it != intervals.end()) {
if (newInterval.end < it->start) {
intervals.insert(it, newInterval);
return intervals;
} else if (newInterval.start > it->end) {
it++;
continue;
} else {
newInterval.start = min(newInterval.start, it->start);
newInterval.end = max(newInterval.end, it->end);
it = intervals.erase(it);
}
}
intervals.insert(intervals.end(), newInterval);
return intervals;
}
};
435
Merge Intervals
相关题目
Insert Interval
436
Minimum Window Substring
描述
Given a string S and a string T , find the minimum window in S which will contain all the characters
in T in complexity O(n) .
Note:
If there is no such window in S that covers all characters in T , return the emtpy string "" .
If there are multiple such windows, you are guaranteed that there will always be only one unique
minimum window in S .
分析
双指针,动态维护一个区间。尾指针不断往后扫,当扫到有一个窗口包含了所有 T 的字符后,然后再收缩
头指针,直到不能再收缩为止。最后记录所有可能的情况中窗口最小的
代码
437
Minimum Window Substring
438
Multiply Strings
Multiply Strings
描述
Given two numbers represented as strings, return multiplication of the numbers as a string.
分析
高精度乘法。
常见的做法是将字符转化为一个int,一一对应,形成一个int数组。但是这样很浪费空间,一个int32的最大
值是 2^{31}-1=2147483647 ,可以与9个字符对应,由于有乘法,减半,则至少可以与4个字符一一对
应。一个int64可以与9个字符对应。
代码1
439
Multiply Strings
// Multiply Strings
// @author 连城 (http://weibo.com/lianchengzju)
// 一个字符对应一个int
// 时间复杂度O(n*m),空间复杂度O(n+m)
typedef vector bigint;
return z;
}
class Solution {
public:
string multiply(string num1, string num2) {
return to_string(make_bigint(num1) * make_bigint(num2));
}
};
代码2
// Multiply Strings
// 9个字符对应一个int64_t
// 时间复杂度O(n*m/81),空间复杂度O((n+m)/9)
/** 大整数类. */
class BigInt {
public:
/**
* @brief 构造函数,将字符串转化为大整数.
440
Multiply Strings
* @param[in] s 输入的字符串
* @return 无
*/
BigInt(string s) {
vector<int64_t> result;
result.reserve(s.size() / RADIX_LEN + 1);
/**
* @brief 大整数乘法.
* @param[in] x x
* @param[in] y y
* @return 大整数
*/
static BigInt multiply(const BigInt &x, const BigInt &y) {
vector<int64_t> z(x.elems.size() + y.elems.size(), 0);
441
Multiply Strings
z[i + j] %= BIGINT_RADIX;
}
}
}
while (z.back() == 0) z.pop_back(); // 没有进位,去掉最高位的0
return BigInt(z);
}
private:
typedef long long int64_t;
/** 一个数组元素对应9个十进制位,即数组是亿进制的
* 因为 1000000000 * 1000000000 没有超过 2^63-1
*/
const static int BIGINT_RADIX = 1000000000;
const static int RADIX_LEN = 9;
/** 万进制整数. */
vector<int64_t> elems;
BigInt(const vector<int64_t> num) : elems(num) {}
};
class Solution {
public:
string multiply(string num1, string num2) {
BigInt x(num1);
BigInt y(num2);
return BigInt::multiply(x, y).toString();
}
};
442
Substring with Concatenation of All Words
描述
You are given a string, S , and a list of words, L , that are all of the same length. Find all starting
indices of substring(s) in S that is a concatenation of each word in L exactly once and without any
intervening characters.
S: "barfoothefoobarman"
L: ["foo", "bar"]
You should return the indices: [0,9] .(order does not matter).
分析
无
代码
443
Substring with Concatenation of All Words
if (--pos->second == 0) unused.erase(pos);
}
return result;
}
};
444
Pascal's Triangle
Pascal's Triangle
描述
Given numRows , generate the first numRows of Pascal's triangle.
Return
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
分析
本题可以用队列,计算下一行时,给上一行左右各加一个0,然后下一行的每个元素,就等于左上角和右上
角之和。
另一种思路,下一行第一个元素和最后一个元素赋值为1,中间的每个元素,等于上一行的左上角和右上角
元素之和。
从左到右
445
Pascal's Triangle
从右到左
相关题目
Pascal's Triangle II
446
Pascal's Triangle
447
Pascal's Triangle II
Pascal's Triangle II
描述
Given an index k , return the k -th row of the Pascal's triangle.
Return [1,3,3,1] .
Note: Could you optimize your algorithm to use only O(k) extra space?
分析
滚动数组。
代码
相关题目
Pascal's Triangle
448
Spiral Matrix
Spiral Matrix
描述
Given a matrix of m × n elements ( m rows, n columns), return all elements of the matrix in spiral
order.
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
分析
模拟。
解法1 迭代
449
Spiral Matrix
解法2 递归
相关题目
Spiral Matrix II
450
Spiral Matrix II
Spiral Matrix II
描述
Given an integer n , generate a square matrix filled with elements from 1 to n^2 in spiral order.
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]
分析
这题比上一题要简单。
代码1
// Spiral Matrix II
// 时间复杂度O(n^2),空间复杂度O(n^2)
class Solution {
public:
vector > generateMatrix(int n) {
vector > matrix(n, vector(n));
int begin = 0, end = n - 1;
int num = 1;
return matrix;
}
};
代码2
451
Spiral Matrix II
相关题目
Spiral Matrix
452
ZigZag Conversion
ZigZag Conversion
描述
The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you
may want to display this pattern in a fixed font for better legibility)
P A H N
A P L S I I G
Y I R
Write the code that will take a string and make this conversion given a number of rows:
分析
要找到数学规律。真正面试中,不大可能出这种问题。
n=4:
P I N
A L S I G
Y A H R
P I
n=5:
P H
A S I
Y I R
P L I G
A N
代码
453
ZigZag Conversion
454
Divide Two Integers
描述
Divide two integers without using multiplication, division and mod operator.
分析
不能用乘、除和取模,那剩下的,还有加、减和位运算。
最简单的方法,是不断减去被除数。在这个基础上,可以做一点优化,每次把被除数翻倍,从而加速。
注意,写代码的时候,禁止使用 long.
代码
455
Divide Two Integers
int result = 0;
while (a >= b) {
int c = b;
for (int i = 0; a >= c;) {
a -= c;
result += 1 << i;
if (c < INT_MAX / 2) { // prevent overflow
++i;
c <<= 1;
}
}
}
456
Text Justification
Text Justification
描述
Given an array of words and a length L , format the text such that each line has exactly L characters
and is fully (left and right) justified.
You should pack your words in a greedy approach; that is, pack as many words as you can in each line.
Pad extra spaces ' ' when necessary so that each line has exactly L characters.
Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a
line do not divide evenly between words, the empty slots on the left will be assigned more spaces than
the slots on the right.
For the last line of text, it should be left justified and no extra space is inserted between words.
For example,
L: 16.
[
"This is an",
"example of text",
"justification. "
]
Corner Cases:
A line other than the last line might contain only one word. What should you do in this case?
In this case, that line should be left
分析
无
代码
// Text Justification
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
vector<string> fullJustify(vector<string> &words, int L) {
vector<string> result;
const int n = words.size();
457
Text Justification
/**
* @brief 添加空格.
* @param[inout]s 一行
* @param[in] i 当前空隙的序号
* @param[in] n 空隙总数
* @param[in] L 总共需要添加的空额数
* @param[in] is_last 是否是最后一行
* @return 无
*/
void addSpaces(string &s, int i, int n, int L, bool is_last) {
if (n < 1 || i > n - 1) return;
int spaces = is_last ? 1 : (L / n + (i < (L % n) ? 1 : 0));
s.append(spaces, ' ');
}
};
458
Text Justification
459
Max Points on a Line
描述
Given n points on a 2D plane, find the maximum number of points that lie on the same straight line.
分析
1
暴力枚举法。两点决定一条直线, n 个点两两组合,可以得到 n(n + 1)条直线,对每一条直线,判
2
断 n 个点是否在该直线上,从而可以得到这条直线上的点的个数,选择最大的那条直线返回。复杂
度 O(n^3) 。
上面的暴力枚举法以“边”为中心,再看另一种暴力枚举法,以每个“点”为中心,然后遍历剩余点,找到所有
的斜率,如果斜率相同,那么一定共线对每个点,用一个哈希表,key为斜率,value为该直线上的点数,
计算出哈希表后,取最大值,并更新全局最大值,最后就是结果。时间复杂度 O(n^2) ,空间复杂
度 O(n) 。
以边为中心
460
Max Points on a Line
以点为中心
461
Max Points on a Line
int count = 0;
if (slope_count.find(slope) != slope_count.end())
count = ++slope_count[slope];
else {
count = 2;
slope_count[slope] = 2;
}
462