给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
说明:
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为 O(1) 的 原地 算法。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if(nums.empty()||k<0) return; // 此处不考虑k为负数的情况
if(k>nums.size()) k = k % nums.size(); // 如果k超过了整个数组长度,则只移动余出的部分
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k , nums.end());
}
};
思路:先反转全部的数组,再按照前K个和size-K个进行分别反转即可;
颠倒(逆序)给定的 32 位无符号整数的二进制位。
输入: 00000010100101000001111010011100
输出: 00111001011110000010100101000000
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
输入:11111111111111111111111111111101
输出:10111111111111111111111111111111
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10101111110010110010011101101001。
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t result = 0;
for(int i=0; i<32; ++i){ //循环32次
result = result << 1; // result左移
if(n&1==1){ // 如果当前n的最后一位是1
result = result | 1; // 对应result中的最后一位也是1
}
n = n >> 1; // n右移
}
return result;
}
};
思路:非常简单,按照32位的每位循环,将n的低位数字赋值到result的高位数字上,每次循环将n右移,将result左移,赋值位置都是最低位;注意与或&和|的运用;
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 ‘1’。
class Solution {
public:
int hammingWeight(uint32_t n) {
int result = 0;
while(n>0){
n = n & (n-1); // 将n和n-1做与运算,消除n最后面的1
++result; // 每消除一个1,计数+1
}
return result;
}
};
思路:这道题可以进行32位循环,判断每一位是否为1;也可以巧用小技巧,没必要判断每一位,而只需要判断有1的位数就行了:n&(n-1)的结果返回的数值是将n的最后一位1变成0的结果,用这个方法可以只进行1的个数次循环;
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
输入:
11110
11010
11000
00000
输出: 1
输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
class Solution {
public:
int numIslands(vector<vector<char> >& grid) {
if(grid.empty()) return 0;
int result = 0;
int row = grid.size(); // 矩阵行数
int col = grid[0].size(); // 矩阵列数
for(int i=0; i<row; ++i){
for(int j=0; j<col; ++j){
if(grid[i][j]=='1'){ // 如果遇到1,则说明登录到了一座新岛
findBorder(grid, i, j); // 勘察该岛,并将其沉没
++result; // 计数+1
}
}
}
return result;
} // 回溯法查找这座岛
void findBorder(vector<vector<char> >& grid, int i, int j){
if(i<0||i>=grid.size()) return;
if(j<0||j>=grid[0].size()) return;
if(grid[i][j]=='0') return;
grid[i][j] = '0';
findBorder(grid, i+1, j);
findBorder(grid, i-1, j);
findBorder(grid, i, j+1);
findBorder(grid, i, j-1);
}
};
思路:比较简单的深度优先遍历路径搜索:遍历整个矩阵,如果遇到1,则说明这肯定是一座岛屿,然后对该坐标处开始路径搜索,将上下左右相邻的1都置为0(相当于沉没这座岛屿),然后路径搜索结束后,继续遍历矩阵,如果又遇到1,则说明这是一座新岛,再次沉没。。。
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
class Solution {
public:
bool isHappy(int n) {
int slow = n, fast = n;
while(1){
slow = myCount(slow); // 每次慢指针推进一次变换
if(slow==1) return true; // 如果等于了1,则是快乐数,返回true
fast = myCount(fast); // 快指针前进两次
fast = myCount(fast);
if(slow==fast) return false; // 如果快慢指针相等了,则说明出现循环,类似于链表找环
}
}
int myCount(int n){
int res = 0;
while(n>0){
res += pow(n%10, 2);
n /= 10;
}
return res;
}
};
思路:首先读清题意,明晰一个整数经过每位平方和运算后一定会一直减小,最后的结果只有变为1和出现无限循环两种情况;问题在于如何判断出现了循环。可以使用一个set容器,存放所有出现过的数,然后如果有数再次出现,则就是循环;这种方法是不错,但是有时循环节可能非常大,所以set占用了额外的内存空间,一种不占用空间的思路是链表找环,把数n的每一次变换状态都看作链表的一个节点,如果出现循环节,相当于这个链表有环,所以同样可以利用快慢指针的思想,slow指针每次进行一次变换,fast指针进行两次变换,如果计算过程中两者相等了,就说明出现了环;
统计所有小于非负整数 n 的质数的数量。
输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
// 常规利用6n+1、6n-1法进行判断
class Solution {
public:
int countPrimes(int n){ // 0和1既不是质数,也不是合数
if(n<=2) return 0; // 题目中判断范围不包括n
if(n<=3) return 1;
if(n<=4) return 2;
int result = 2; // 预先存储两个质数2和3
for(int i=5; i<n; i+=2){ // 遍历所有数(只遍历奇数即可)
if(judge(i)) ++result; // 如果是质数,计数+1
}
return result;
}
bool judge(int n){ // 该函数只用来判断5以后的质数
if( n%6!=1 && n%6!=5 ) return false; // 不是6的倍数两侧的数,一定不是质数
double sqrtm = sqrt(n); // 是6的倍数两侧的数,进一步判断是不是质数
for(int i=5; i<=sqrtm; i+=6){ // 利用(5到根号n)之间的一些除数进行判断
if( n%i==0 || n%(i+2)==0 ){ // 如果可以被5到根号n之间的(6两侧数)整除,则说明也不是质数
return false;
}
}
return true;
}
};
// 厄拉多塞筛法
class Solution {
public:
int countPrimes(int n) {
if(n<=2) return 0;
int result = 0;
int list[n]; // n长度的数组,初始化默认值都为0,为质数标记
memset(list, 0, sizeof(list));
if(n>2) ++result; // 如果大于 2 则一定拥有 2 这个质数
for(int i=3; i<n; i+=2){ // 从 3 开始遍历,且只遍历奇数
if(list[i]==0){ // 当前数值i的质数标记 是0,即是质数
for(int j=3; i*j<n; j+=2){
list[i*j] = 1; // 将当前质数的奇数倍都设置成 非质数标记 1
}
++result; // 质数个数 +1
}
}
return result;
}
};
思路:首先明确0和1不是质数也不是合数;明确题目要求在小于n的数中的质数个数;除了质数2和3,其他质数质数一定符合6n+1或6n-1的公式,但满足公式的数不全是质数;可以根据这一点进行判断;首先判断n是否是6的倍数两侧的数,如果不是,肯定不是质数;如果是两侧的数,则进一步判断是否为质数:让n除以i,如果能整除,则说明不是质数(i的选取是从5到根号n之间的数,且i只需要选择这个区间内的6的倍数两侧的数即可);其中还有一点是判断每个数是不是质数的遍历中,只需要遍历奇数就好了;
该题还有很多其他筛选法,如厄拉多塞筛法,这种方法借助于长度为n的质数标记数组,从小到大进行判断和计数(也是只遍历奇数),从3开始,3为质数,所以在n范围内,3的奇数倍(除去自身)的值都不是质数,然后遍历5,把5的奇数倍都设定为非质数…这种方法的计算效率要更高一些;
反转一个单链表。
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==nullptr) return nullptr;
ListNode * left = nullptr;
ListNode * mid = head;
ListNode * right = head;
while(right){
right = mid->next;
mid->next = left;
left = mid;
mid = right;
}
return left;
}
};
思路:基础题,设置左中右三个指针,开始的时候左指针指向头结点的前一个位置,结束的时候右节点指向尾结点的后一个位置;注意反转过程的逻辑就好;
你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
提示:
输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
1 <= numCourses <= 10^5
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
if(numCourses<=0 || prerequisites.empty()) return true;
queue<int> q; // 队列,存放当前入度为0的课程节点
vector<int> inDegree(numCourses, 0); // 统计每个节点的入度
vector<vector<int> > list(numCourses); // 邻接表,表示每个节点为头部所指向的边
vector<int> result; // 存放出队列的节点
for(auto v : prerequisites){ // 遍历课程依赖数组,统计每门课在有向图节点的入度
++inDegree[v[1]]; // 累计入度数
list[v[0]].push_back(v[1]); // 将对应的边添加到邻接表
}
for(int i=0; i<numCourses; ++i){ // 遍历入度统计数组,先将第一批入度为0的节点入队
if(inDegree[i]==0){
q.push(i);
}
}
while(!q.empty()){
int tmp = q.front(); // 获取当前的队列头部,一个入度为0的节点,将其对应的依赖节点入度-1
q.pop(); // 该节点出栈
result.push_back(tmp); // 出栈的节点由result保存
for(auto i : list[tmp]){ // 遍历邻接表,以当前节点为头部的边的入度都-1
if(--inDegree[i]==0){ // 对应的节点入度-1,如果为0了,则继续分离进队列
q.push(i);
}
}
} // 循环完毕,队列为空,只要有节点的入度还不是0(没入栈出栈,result中没有此节点),说明肯定有环存在
return result.size()==numCourses;
}
};
思路:这道题的思路和领域属于图,这类的题不多但很有必要做一做;这道题主要问题在于解决互相依赖问题,有点类似于在资源图中搜寻产生死锁的必要条件:环。比较经典的方法是图的拓扑排序:拓扑排序指的是在一个有向无环图(DAG)中,对所有的节点进行排序,要求没有一个节点指向它前面的节点。
步骤:
先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减一。
一直做改操作,直到所有的节点都被分离出来。
如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况。
具体的代码思路是建立一个数组统计节点的入度,建立一个二维数组作为图的邻接表(一维下标表示节点序号,下标指示的vector存储以该节点为头部的边的尾结点,(i,j)表示一条特定的边),利用一个队列将每次入度变为0的节点入队,直到队列为空(没有剩余入度为0的节点了),统计所有入度变为0的节点数,如果等于开始的课程数,说明没有环,即可以学完;
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
Trie trie = new Trie();
trie.insert(“apple”);
trie.search(“apple”); // 返回 true
trie.search(“app”); // 返回 false
trie.startsWith(“app”); // 返回 true
trie.insert(“app”);
trie.search(“app”); // 返回 true
你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。
class Trie { // 前缀树的节点类(注意根结点处是不代表字符的)
private:
bool isEnd; // 表示当前节点是不是一个字符串的终止节点
Trie * next[26]; // 当前节点指向之后26个字符节点的数组
public:
Trie() { // 前缀树节点的构造函数
isEnd = false;
memset(next, 0, sizeof(next));
}
void insert(string word) { // 在当前节点之后插入新string
Trie * node = this; // 用于获取当前节点的指针
for(char c : word){
if(node->next[c-'a']==nullptr){ // 若之后的指针没有指向新节点
node->next[c-'a'] = new Trie(); // new一个新节点
}
node = node->next[c-'a']; // 节点指针指向刚new出来的节点
}
node->isEnd = true; // 将最后一个new出来的终止节点设定为end
}
bool search(string word) { // 从当前节点之后查找一个字符串(必须到头为end标识时才算查找到)
Trie * node = this;
for(char c : word){
if(node->next[c-'a']==nullptr){ // 如果之后没有字符节点了,则查找失败
return false;
}
node = node->next[c-'a'];
}
return node->isEnd; // 查找完最后一个字符,如果该节点有终止标识,则说明查找成功
}
bool startsWith(string prefix) { // 从当前节点之后查找有没有这个前缀(不用到头,标识不用是end)
Trie * node = this;
for(char c : prefix){
if(node->next[c-'a']==nullptr){
return false;
}
node = node->next[c-'a'];
}
return true;
}
};
思路:很好的题,考察了高级数据结构(前缀树、Trie、字典树)的多叉树数据结构问题;首先必须明确前缀树的基本定义、性质和数据结构:1、根节点不包含字符,除根节点外每一个节点都只包含一个字符; 2、从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 3、每个节点的所有子节点包含的字符都不相同。明晰之后才能写出该题的代码;一个需要注意的地方是:代码中的额类Trie是前缀树的节点类,前缀树的查找、搜索前缀的方法可以从任意节点处开始,且不包括这个开始节点,要从该节点的下一个节点处开始匹配第一个字符,巧用this获取当前类的指针;
现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
输入: 2, [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
说明:
输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
提示:
这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
拓扑排序也可以通过 BFS 完成。
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
vector<int> result;
if(numCourses<=0) return result;
vector<int> inDegree(numCourses, 0); // 统计入度情况
vector<vector<int> > edge(numCourses); // 统计边信息的二维数组
queue<int> q; // 入度为0的节点队列q
for(auto v : prerequisites){ // 统计入度和边信息
edge[v[1]].push_back(v[0]);
++inDegree[v[0]];
}
for(int i=0; i<numCourses; ++i){ // 第一批入度为0的入栈
if(inDegree[i]==0){
q.push(i);
}
}
while(!q.empty()){ // 循环处理0入度节点,将其下一节点入度-1
int tmp = q.front();
q.pop();
result.push_back(tmp); // 输出最后结果
for(auto i : edge[tmp]){ // 检索所有以这个节点为起始的边
if(--inDegree[i]==0){
q.push(i); // 新的节点入度为0,入栈
}
}
}
// 检查是否所有节点最后的入度都为0,否则返回空数组
if(result.size()!=numCourses) result.clear();
return result;
}
};
思路:是题目207、课程表的变形,整体思路是一模一样的,用到了图结构中的拓扑排序,只不过这道题是将拓扑排序的结果输出而已;
给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
输入:
words = [“oath”,“pea”,“eat”,“rain”] and board =
[‘o’,‘a’,‘a’,‘n’],
[‘e’,‘t’,‘a’,‘e’],
[‘i’,‘h’,‘k’,‘r’],
[‘i’,‘f’,‘l’,‘v’]
输出: [“eat”,“oath”]
说明:你可以假设所有输入都由小写字母 a-z 组成。
提示:
你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?
如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?
struct Node { // 前缀树的节点(每个节点对应一个字符)
bool word; // 该节点处是否形成了一个单词
string str; // 该节点形成的单词
unordered_map<char, Node*> words; // 哈希表,存放该节点的子节点中记录的字符(节点)
};
class Trie { // 前缀树类
private:
Node* root; // 树对象有一个根节点
public:
Trie() { // 前缀树构造,生成根节点
root = new Node();
}
void insert(string word) { // 前缀树中插入string单词
Node* p = root; // 获取根节点
for (char c: word) { // 遍历每个字符
if (p->words.find(c) == p->words.end()) { // 如果该节点的子节点中没有这个字符
Node* t = new Node(); // 新创建一个节点t存放新的字符
p->words[c] = t; //
}
p = p->words[c]; // 往字符c的子节点移动,继续插入单词的下一个字符
}
p->str = word; // 插入word完毕,对应节点直接保存这个单词,方便后续使用
p->word = true; // 对应节点为true,表示该节点处形成了一个单词
}
void search(vector<string>& res, vector<vector<char>>& board) { // 在前缀树中查找网格中的所有坐标点
for (int i = 0; i < board.size(); i++) {
for (int j = 0; j < board[i].size(); j++) {
help(res, board, root, i, j); // 以当前网格坐标对应的字符开始搜索单词是否存在
}
}
} // 以当前网格坐标对应的字符开始搜索单词是否存在,存在则放入res结果数组
void help(vector<string>&res, vector<vector<char>>& board, Node* p, int x, int y) {
if (p->word) { // 如果当前节点true,表示是一个单词结束节点
p->word = false; // 重置为false,其他方向就不会再把答案放进去了
res.push_back(p->str); // 找到了一个单词,放入结果数组
return;
}
if (x < 0 || x == board.size() || y < 0 || y == board[x].size()) return; // 当前坐标越界
if (p->words.find(board[x][y]) == p->words.end()) return; // 当前节点的子节点中没有对应字符,说明搜索失败
p = p->words[board[x][y]]; // 获得当前节点的对应字符的子节点
char cur = board[x][y]; // 当前字符cur
board[x][y] = '#'; // 当前字符设为#号,避免后续DFS时重复进入该节点搜索
help(res, board, p, x+1, y);
help(res, board, p, x-1, y);
help(res, board, p, x, y+1);
help(res, board, p, x, y-1);
board[x][y] = cur; // 以该坐标处为开始部分的搜索结束,重置坐标字符
}
};
class Solution {
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
Trie trie; // 初始化一个前缀树trie对象,其中包括了一个根节点Node
vector<string> res; // 结果数组
for (string& w: words) { // 遍历字典中所有的词,将其插入前缀树对象中
trie.insert(w);
}
trie.search(res, board); // 在前缀树中查找整个网格,如果遇到了前缀树中存储的单词,则输出
return res;
}
};
思路:困难的题,可以简单了解即可;这道题的主要问题在于:如果是在一个矩阵中搜索一个string的话,利用回溯路径搜索即可解决;但这道题要搜索若干个string,如果对每个string都回溯一遍,效率会很低,尤其是遇到很多前缀相同的单词,那么对矩阵中的某个点处可能要做很多次回溯路径搜索,哈希表可以试着解决这个问题,但可能比较复杂,因为哈希表很难对单词的部分前缀进行索引;好的办法是利用前缀树+回溯搜索,前缀树的结构特点可以轻松地代替原先的string数组,并且相同前缀的string都具有相同的根节点,首先利用题目给出的string数组构造一棵前缀树,然后常规使用回溯算法的时候顺便在前缀树中进行搜索判断,路径搜索每前进一步,前缀树的搜索就前进一个节点,并且这样前缀相同的string部分就不用重复回溯了,回溯只会回溯到string不同的字符的位置;不太好理解,大体了解即可;
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
#include // 大小顶堆算法的头文件
#include // function容器,用于包装可调用对象
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
function<bool(int a,int b)> comp; // function容器,用于包装可调用对象
if(k<=nums.size()/2){ // k比较小,常规建立小根堆搜索第k大即可
comp = greater<int>(); // 利用function包装小顶堆的比较操作
}
else{ // k比较大,建立的堆可能会很大,故问题可以转化为寻找第(size-k+1)小的值,利用的是最大堆
k = nums.size()-k+1; // 把k改为“第k小个数值”,并利用大顶堆
comp = less<int>(); // 利用function包装大顶堆的比较操作
}
// 后面的注释以第一种情况为准(小顶堆)
vector<int> v(nums.begin(), nums.begin()+k); // 建立数组, 预存nums中的前k个数值
make_heap(v.begin(), v.end(), comp); // 整理成小顶堆
for(int i=k; i<nums.size(); ++i){ // 遍历k之后的所有元素
if(comp(nums[i], v.front())){ // 如果当前的元素更大,则取出小顶堆的头部,把新值放入小顶堆
pop_heap(v.begin(), v.end(), comp); // 取出小顶堆头部,放置在数组最后
v.pop_back(); // 将这个值弹出
v.push_back(nums[i]); // 加入新值
push_heap(v.begin(), v.end(),comp); // 将尾部的新值一起再整理成小顶堆
}
}
return v.front(); // 返回小顶堆的头部(第k小)
}
};
思路:如果按照常规思路,先排序,在输出,复杂度是O(nlogn),如果使用小根堆解决问题,则需要遍历一次数组,每次遍历数组时在小根堆的插入删除操作复杂度是O(logk),k是小根堆的元素大小(取值1~n),故总复杂度是O(nlogk),稍稍能够减少计算复杂度,上述的代码还利用了如果k很大,则转化为求解第(size-k+1)的最小的数的问题,利用了function容器包装可调用对象;需要熟悉C++ STL中的大小根堆泛型算法;另外需要注意的是:STL中的适配器priority_queue优先级队列的底层实现其实就是利用了make_heap等泛型算法的vector,如priority_queue < int, vector, greater > q; 故该题也可以使用priority_queue来做,更加简便,但需要清楚其底层实现和计算复杂度;
给定一个整数数组,判断是否存在重复元素。
如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。
输入: [1,2,3,1]
输出: true
输入: [1,2,3,4]
输出: false
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> list(nums.begin(), nums.end());
return (list.size()!=nums.size());
}
};
思路:没啥技巧的题,思路很多,先排序,再查找,或者建立哈希表,然后比较等等,从测试结果来看使用快排要比使用哈希表的方式更快一些;