204. 计数质数
给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0
输出:0
示例 3:
输入:n = 1
输出:0
提示:
0 < = n < = 5 ∗ 1 0 6 0 <= n <= 5 * 10^6 0<=n<=5∗106
思路1:如果一个数是质数,那么它*2,*3……等,也都是质数。
class Solution
{
public:
int countPrimes(int n)
{
vector<int> isPrime(n, 1);
int res = 0;
for (int i = 2; i < n; i++)
{
if (isPrime[i])
{
res++;
for (int j = 2; i * j < n; j++)
{
isPrime[i * j] = 0;
}
}
}
return res;
}
};
思路2:可以对其进行优化,不一定要从2开始,可以直接从当前数i进行翻倍,因为从2开始,其实之前已经计算过了。
class Solution
{
public:
int countPrimes(int n)
{
vector<int> isPrime(n, 1);
int res = 0;
for (int i = 2; i < n; i++)
{
if (isPrime[i])
{
res++;
for (int j = i; (long long)i * j < n; j++)
{
isPrime[i * j] = 0;
}
}
}
return res;
}
};
思路3:参考了官方题解的优化方法,感觉有些鸡肋了,虽然优化了重复计算,但引入了一个取余运算。数学上复杂度确实降低了,但if分支所需要的复杂度也不可忽略。1
class Solution
{
public:
int countPrimes(int n)
{
vector<int> primes;
vector<int> isPrime(n, 1);
int res = 0;
for (int i = 2; i < n; i++)
{
if (isPrime[i])
{
primes.push_back(i);
}
for (int j = 0; j < primes.size() & i * primes[j] < n; j++)
{
isPrime[i * primes[j]] = 0;
if ((i % primes[j]) == 0)
{
break;
}
}
}
return primes.size();
}
};
从实际效果来看,思路3并没有快到哪里去。
思路2测试结果:
思路3测试结果:
210. 课程表 II
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
示例 3:
输入:numCourses = 1, prerequisites = []
输出:[0]
提示:
1
<
=
n
u
m
C
o
u
r
s
e
s
<
=
2000
1 <= numCourses <= 2000
1<=numCourses<=2000
0
<
=
p
r
e
r
e
q
u
i
s
i
t
e
s
.
l
e
n
g
t
h
<
=
n
u
m
C
o
u
r
s
e
s
∗
(
n
u
m
C
o
u
r
s
e
s
−
1
)
0 <= prerequisites.length <= numCourses * (numCourses - 1)
0<=prerequisites.length<=numCourses∗(numCourses−1)
p
r
e
r
e
q
u
i
s
i
t
e
s
[
i
]
.
l
e
n
g
t
h
=
=
2
prerequisites[i].length == 2
prerequisites[i].length==2
0
<
=
a
i
,
b
i
<
n
u
m
C
o
u
r
s
e
s
0 <= a_i, b_i < numCourses
0<=ai,bi<numCourses
a
i
!
=
b
i
a_i != b_i
ai!=bi
所有[ai, bi] 互不相同
bfs逻辑上更好理解一些,题解也有采用dfs实现的方式2
#include <vector>
#include <queue>
using namespace std;
class Solution
{
public:
vector<int> findOrder(int numCourses, vector<vector<int>> &prerequisites)
{
vector<vector<int>> graph(numCourses, vector<int>()); // 邻接表
vector<int> indegree(numCourses, 0); // 入度
for (int i = 0; i < prerequisites.size(); i++)
{
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
indegree[prerequisites[i][0]]++;
}
queue<int> curCourses;
vector<int> res;
for (int i = 0; i < numCourses; i++)
{
if (indegree[i] == 0)
{
res.push_back(i);
curCourses.push(i);
}
}
while (!curCourses.empty())
{
int curCourse = curCourses.front();
curCourses.pop();
for (int i = 0; i < graph[curCourse].size(); i++)
{
indegree[graph[curCourse][i]]--;
if (indegree[graph[curCourse][i]] == 0)
{
res.push_back(graph[curCourse][i]);
curCourses.push(graph[curCourse][i]);
}
}
}
if (res.size() == numCourses)
{
return res;
}
else
{
return {};
}
}
};
212. 单词搜索 II
给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例 1:
输入:board = [[“o”,“a”,“a”,“n”],[“e”,“t”,“a”,“e”],[“i”,“h”,“k”,“r”],[“i”,“f”,“l”,“v”]], words = [“oath”,“pea”,“eat”,“rain”]
输出:[“eat”,“oath”]
示例 2:
输入:board = [[“a”,“b”],[“c”,“d”]], words = [“abcb”]
输出:[]
提示:
m
=
=
b
o
a
r
d
.
l
e
n
g
t
h
m == board.length
m==board.length
n
=
=
b
o
a
r
d
[
i
]
.
l
e
n
g
t
h
n == board[i].length
n==board[i].length
1
<
=
m
,
n
<
=
12
1 <= m, n <= 12
1<=m,n<=12
b
o
a
r
d
[
i
]
[
j
]
board[i][j]
board[i][j] 是一个小写英文字母
1
<
=
w
o
r
d
s
.
l
e
n
g
t
h
<
=
3
∗
1
0
4
1 <= words.length <= 3 * 10^4
1<=words.length<=3∗104
1
<
=
w
o
r
d
s
[
i
]
.
l
e
n
g
t
h
<
=
10
1 <= words[i].length <= 10
1<=words[i].length<=10
w
o
r
d
s
[
i
]
words[i]
words[i] 由小写英文字母组成
w
o
r
d
s
words
words 中的所有字符串互不相同
很自然可以想到回溯法,但是回溯法会超时:
#include <vector>
#include <string>
#include <map>
using namespace std;
class Solution
{
int directX[4] = {-1, 1, 0, 0};
int directY[4] = {0, 0, -1, 1};
bool findWordInBoard(vector<vector<char>> &board, string &word, vector<vector<bool>> &visited, int x, int y, int curPos)
{
bool res = false;
for (int i = 0; i < 4; i++)
{
if (x < 0 || x >= board.size() || y < 0 || y > board[0].size())
{
continue;
}
if (!visited[x][y] && word[curPos] == board[x][y])
{
visited[x][y] = true;
if (curPos + 1 == word.size())
{
res = true;
}
else
{
res = findWordInBoard(board, word, visited, x + directX[i], y + directY[i], curPos + 1);
}
visited[x][y] = false;
}
if (res == true)
{
break;
}
}
return res;
}
public:
vector<string> findWords(vector<vector<char>> &board, vector<string> &words)
{
vector<string> res;
for (int i = 0; i < words.size(); i++)
{
string word = words[i];
bool exist = false;
for (int x = 0; x < board.size(); x++)
{
for (int y = 0; y < board[0].size(); y++)
{
vector<vector<bool>> visited(board.size(), vector<bool>(board[0].size()));
exist = findWordInBoard(board, word, visited, x, y, 0);
if (exist)
{
res.push_back(word);
break;
}
else
{
continue;
}
}
if (exist)
{
break;
}
}
}
return res;
}
};
int main()
{
vector<vector<char>> board = {{'o', 'a', 'a', 'n'}, {'e', 't', 'a', 'e'}, {'i', 'h', 'k', 'r'}, {'i', 'f', 'l', 'v'}};
vector<string> words = {"oath", "pea", "eat", "rain"};
Solution s;
vector<string> res = s.findWords(board, words);
for (int i = 0; i < res.size(); i++)
{
printf("%s\n", res[i].c_str());
}
return 0;
}
需要利用字典树(可以参考我的另外一篇博客中,关于Trie的实现:https://blog.csdn.net/myf_666/article/details/131379343)进行优化,这里也有一点不好想。优化的思路是,先把words中的单词全部插入到字典树中来,然后对board中的每一个块进行dfs,dfs的过程中判断当前路径是否startsWith,但还不能用startsWith这个函数,因为这样没有很好的利用到之前的信息,相当于进行了重复计算。(也会超时)
参照题解3,一种更好的做法是,不断地移动Trie指针,这样就不需要判断前边是否满足条件了,只需要判断当前遍历的字母是否满足条件。
代码如下:
class Trie
{
public:
vector<Trie *> children;
string word;
Trie()
{
children = vector<Trie *>(26, nullptr);
word = "";
}
void insert(string word)
{
Trie *curNode = this;
for (int i = 0; i < word.size(); i++)
{
int index = word[i] - 'a';
if (curNode->children[index] == nullptr)
{
curNode->children[index] = new Trie();
}
curNode = curNode->children[index];
}
curNode->word = word;
}
bool search(string word)
{
Trie *curNode = this;
for (int i = 0; i < word.size(); i++)
{
if (curNode->children[word[i] - 'a'] == nullptr)
{
return false;
}
curNode = curNode->children[word[i] - 'a'];
}
// 搜索存在,表示的是必须要以这个单词为结尾
return curNode != nullptr && curNode->word != "";
}
bool startsWith(string prefix)
{
Trie *curNode = this;
for (int i = 0; i < prefix.size(); i++)
{
if (curNode->children[prefix[i] - 'a'] == nullptr)
{
return false;
}
curNode = curNode->children[prefix[i] - 'a'];
}
return curNode != nullptr;
}
};
class Solution
{
int directX[4] = {-1, 1, 0, 0};
int directY[4] = {0, 0, -1, 1};
bool dfs(vector<vector<char>> &board, int x, int y, Trie* trie, set<string> &res)
{
char ch = board[x][y];
if (trie->children[ch - 'a'] == nullptr)
{
return false;
}
trie = trie->children[ch - 'a'];
if (trie->word != "")
{
res.insert(trie->word);
}
board[x][y] = '#';
for (int i = 0; i < 4; i++)
{
int newX = x + directX[i];
int newY = y + directY[i];
if (newX < 0 || newX >= board.size() || newY < 0 || newY >= board[0].size())
{
continue;
}
if (board[newX][newY] != '#')
{
dfs(board, newX, newY, trie, res);
}
}
board[x][y] = ch;
return true;
}
public:
vector<string> findWords(vector<vector<char>> &board, vector<string> &words)
{
Trie* trie = new Trie();
for (int i = 0; i < words.size(); i++)
{
trie->insert(words[i]);
}
set<string> res;
for (int x = 0; x < board.size(); x++)
{
for (int y = 0; y < board[0].size(); y++)
{
dfs(board, x, y, trie, res);
}
}
return vector<string>(res.begin(), res.end());
}
};
运行后发现已经AC,但时间较长:
还可以对已经搜索过的单词去重,这样就不需要利用set进行去重了。可以进一步优化减少时间。
class Solution
{
int directX[4] = {-1, 1, 0, 0};
int directY[4] = {0, 0, -1, 1};
bool dfs(vector<vector<char>> &board, int x, int y, Trie *trie, vector<string> &res)
{
char ch = board[x][y];
if (trie->children[ch - 'a'] == nullptr)
{
return false;
}
trie = trie->children[ch - 'a'];
if (trie->word != "")
{
res.push_back(trie->word);
trie->word = "";
}
board[x][y] = '#';
for (int i = 0; i < 4; i++)
{
int newX = x + directX[i];
int newY = y + directY[i];
if (newX < 0 || newX >= board.size() || newY < 0 || newY >= board[0].size())
{
continue;
}
if (board[newX][newY] != '#')
{
dfs(board, newX, newY, trie, res);
}
}
board[x][y] = ch;
return true;
}
public:
vector<string> findWords(vector<vector<char>> &board, vector<string> &words)
{
Trie *trie = new Trie();
for (int i = 0; i < words.size(); i++)
{
trie->insert(words[i]);
}
vector<string> res;
for (int x = 0; x < board.size(); x++)
{
for (int y = 0; y < board[0].size(); y++)
{
dfs(board, x, y, trie, res);
}
}
return res;
}
};
217. 存在重复元素
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
示例 1:
输入:nums = [1,2,3,1]
输出:true
示例 2:
输入:nums = [1,2,3,4]
输出:false
示例 3:
输入:nums = [1,1,1,3,3,4,3,2,4,2]
输出:true
提示:
1
<
=
n
u
m
s
.
l
e
n
g
t
h
<
=
1
0
5
1 <= nums.length <= 10^5
1<=nums.length<=105
−
1
0
9
<
=
n
u
m
s
[
i
]
<
=
1
0
9
-10^9 <= nums[i] <= 10^9
−109<=nums[i]<=109
最直观的想法就是利用哈希进行一个判重,遍历数组,如果发现了重复,那么就直接return true,否则,最后return false。
class Solution
{
public:
bool containsDuplicate(vector<int> &nums)
{
unordered_set<int> st;
for (auto it : nums)
{
if (st.find(it) != st.end())
{
return true;
}
st.insert(it);
}
return false;
}
};
因为哈希表在空间上有O(N)的开销,如果想进一步在空间复杂度上优化的话,还可以利用排序的方法,比如快排,通过快排,可以实现O(logn)的空间复杂度,递归栈的空间复杂度。
class Solution
{
public:
bool containsDuplicate(vector<int> &nums)
{
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 1; i++)
{
if (nums[i] == nums[i + 1])
{
return true;
}
}
return false;
}
};
https://leetcode.cn/problems/count-primes/solutions/507273/ji-shu-zhi-shu-by-leetcode-solution/ ↩︎
https://leetcode.cn/problems/course-schedule-ii/solutions/249149/ke-cheng-biao-ii-by-leetcode-solution/ ↩︎
https://leetcode.cn/problems/word-search-ii/solutions/1000172/dan-ci-sou-suo-ii-by-leetcode-solution-7494/ ↩︎