1. JZ53 数字在升序数组中出现的次数
1. 自己的二分查找,直接查找k
题目很简单,使用二分查找即可,我的想法很普通,直接找k的边界,然后相减,求得出现的次数。
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
if(data.size()==0) return 0;
int left = 0;
int right = data.size()-1;
int mid = (left+right)/2;
int Res = 0;
while(data[mid]!=k){
if(data[mid]>k){
right = mid-1;
mid = (left+right)/2;
}
else if(data[mid]<k){
left = mid+1;
mid = (left+right)/2;
}
if(left>right) return 0;
}
int i=mid;
int j=mid;
for(; i>=left || j<=right; ){
if(data[i]==k) --i;
if(data[j]==k) ++j;
if(data[i]!=k && data[j]!=k) break;
}
return j-i-1;
}
};
2. 官方查找k±0.5的Trick(推荐)
由于数据都是整数,所以可以找浮点数k+0.5和k-0.5,注意返回的都是左边界,然后相减即可。
class Solution {
public:
int BiSearch(vector<int> data, float k){
int left = 0;
int right = data.size()-1;
int mid = 0;
while(left<=right){
mid = (left+right)/2;
if(data[mid]>k) right = mid-1;class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.size()==0 || array[0].size()==0) return false;
int mid = 0;
for(int row=0; row<array.size(); ++row){
int left = 0;
int right = array[0].size()-1;
if(array[row][left]<=target && array[row][right]>=target){
while(left<=right){
mid = (left+right)/2;
if(array[row][mid]>target) right = mid-1;
else if(array[row][mid]<target) left = mid+1;
else return true;
}
}
}
return false;
}
};
else if(data[mid]<k) left = mid+1;
}
return left; //返回left或者right都对
}
int GetNumberOfK(vector<int> data ,int k) {
return BiSearch(data, k+0.5) - BiSearch(data, k-0.5);
}
};
正常的二分查找:
3. 本题小结
- 二分查找的时间复杂度为 l o g ( N ) log(N) log(N)
- 可以查找刚好大于且刚好小于待查数据的位置,因为查不到,所以最后的结果是刚好左右边界都越界,但是返回left或者right都对,相减即可。
2. JZ11 旋转数组的最小数字
1. 官方解法(推荐,唯手熟尔)
循环条件中不能加等号,相等才跳出的,证明找到了,所以返回data[right]和data[left]都对。
思想还是二分法查找,
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int left = 0;
int right = rotateArray.size() - 1;
while(left < right){ //这个不能加等号,l=r就证明找到了最小值,应直接输出
int mid = (left + right) / 2;
//最小的数字在mid右边
if(rotateArray[mid] > rotateArray[right])
left = mid + 1;
//无法判断,一个一个试
else if(rotateArray[mid] == rotateArray[right])
right--;
//最小数字要么是mid要么在mid左边
else
right = mid;
}
return rotateArray[left]; //返回right也行,因为都是相等时才跳出的
}
};
2. 暴力解法
找到第一个data[i]>data[i+1]的,return data[i+1]
时间复杂度
O
(
N
)
O(N)
O(N),空间复杂度
O
(
1
)
O(1)
O(1)
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int min = rotateArray[0];
for(int i=0;i<rotateArray.size()-1;++i){
if(rotateArray[i]>rotateArray[i+1]){
return rotateArray[i+1];
}
}
return min;
}
};
3. 本题小结
- 主要是含有相等元素的时候不好处理,只能一个个找,唯手熟尔。
3. JZ44 数字序列中某一位的数字
题目意思就是数据就是按照0123456789101112…这样来排列的,求第n位,不需要原数据,直接可以求第n位。
1. 位数相减
统计累积到n位数总共有多少位,如果超过了n,则在n位数范围内。
class Solution {
public:
// num是内置的字符串
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @return int整型
*/
int findNthDigit(int n) {
// write code here
int digit = 0;
long long sum = 0;
if(n<10) return n;
while(true){
++digit;
//计算该位数的数字区间的总数字位数
if(digit==1) sum=10;
else sum=digit * (9 * pow(10, digit-1));
//找到了第n位是多少位数的区间
if(n-sum<0){
unsigned int quotient = n/digit;
unsigned int remainder = n%digit;
unsigned int target_num = pow(10, digit-1) + quotient;
string str{to_string(target_num)};
int result = str[remainder]-'0';
return result;
}
else n-=sum;
}
}
};
2. 补0法(推荐)
简单易操作。记住15这个例子。
class Solution {
public:
int findNthDigit(int n) {
//记录n是几位数
int i = 1;
while(i * pow(10, i) < n) //每次加完0之后都是i*pow(10,i)个数
{
//前面添0增加的位
n += pow(10, i); //加10^i而非(i-1)
i++;
}
//根据除法锁定目标数字,根据取模锁定位置
return to_string(n / i)[n % i] - '0';
}
};
1. 本题小结
- 推荐补零法,好操作。添加0的个数需要琢磨。
4. JZ4 二维数组中的查找
1. 我的暴力做法
没有什么技巧,判断每行第一个和最后一个是否符合要求,如果min<=target && max>=target就对这行进行二分查找,如果找到就true,如果没找到就继续下一个循环,若到最后都没找到的话,就false。
对于下图所示的情况,还是基本上每行都要找,效率不高。
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.size()==0 || array[0].size()==0) return false;
int mid = 0;
for(int row=0; row<array.size(); ++row){
int left = 0;
int right = array[0].size()-1;
if(array[row][left]<=target && array[row][right]>=target){
while(left<=right){
mid = (left+right)/2;
if(array[row][mid]>target) right = mid-1;
else if(array[row][mid]<target) left = mid+1;
else return true;
}
}
}
return false;
}
};
2. 官方分析法
根据分析,每行第一个最小,最后一个最大,那么左上角的最小,右下角的最大,可以得到:
左下角>左上角,左下角<右下角
官方思路从左下角开始找,若该值<target,则列数++,若该值>target,则行数–,最后在不越界的情况下如果找到相等的,则true,否则false。
如动图所示
我仿照这个思想从右上角开始找,若该值<target,则行数++,若该值>target,则列数–
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.size()==0 || array[0].size()==0) return false;
int row = 0;
int colum = array[0].size()-1;
while(row<=array.size()-1 && colum >=0){
if(array[row][colum]<target) ++row;
else if(array[row][colum]>target) --colum;
else return true;
}
return false;
}
};
3. 本题小结
- 本题使用了数组递增的特性,我理解实际上就是用这个特性找找到这个值的最短路径。
5. JZ38 字符串的排列
1. 自己的递归
思想:
对于每个段,交换第一个和后面的一个,然后对交换后的后面的进行递归,直到递归到只有一个字符时,然后就开始返回,每次都返回递归的子段的的结果vector,返回回来后哦,将第一个和所有结果相加,再返回。
时间复杂度
O
(
N
!
)
O(N!)
O(N!),因为对每个子段都要递归,空间复杂度也为
O
(
N
!
)
O(N!)
O(N!)
class Solution {
public:
vector<string> Recursion(string str){
if(str.size()==1) return vector<string>{str};
vector<string> Res{};
for(int i=0; i<str.size();++i){
string TmpStr = str; //回溯
if(TmpStr[0]==TmpStr[i] && i!=0) continue; //与首字母相同的不交换
if(TmpStr[i]==TmpStr[i-1]) continue; //连续与前面相同的不交换
//交换首字母与后面的,然后递归后面的
auto Tmp = TmpStr[0];
TmpStr[0] = TmpStr[i];
TmpStr[i] = Tmp;
vector<string> TmpRes = Recursion(TmpStr.substr(1, TmpStr.size()-1));
for(auto each:TmpRes)
Res.push_back(TmpStr[0]+each);
}
return Res;
}
vector<string> Permutation(string str) {
vector<string> Res{};
if(str.size()==0) return Res;
return Recursion(str);
}
};
2. 官方
官方解析太绕了,又是递归又是回溯,还要排序。
class Solution {
public:
void recursion(vector<string> &res, string &str, string &temp, vector<int> &vis){
//临时字符串满了加入输出
if(temp.length() == str.length()){
res.push_back(temp);
return;
}
//遍历所有元素选取一个加入
for(int i = 0; i < str.length(); i++){
//如果该元素已经被加入了则不需要再加入了
if(vis[i])
continue;
if(i > 0 && str[i - 1] == str[i] && !vis[i - 1])
//当前的元素str[i]与同一层的前一个元素str[i-1]相同且str[i-1]已经用过了
continue;
//标记为使用过
vis[i] = 1;
//加入临时字符串
temp.push_back(str[i]);
recursion(res, str, temp, vis);
//回溯
vis[i] = 0;
temp.pop_back();
}
}
vector<string> Permutation(string str) {
//先按字典序排序,使重复字符串相邻
sort(str.begin(), str.end());
//标记每个位置的字符是否被使用过s
vector<int> vis(str.size(), 0);
vector<string> res;
string temp;
//递归获取
recursion(res, str, temp, vis);
return res;
}
};
3. 调函数next_permutation(begin, end)
这个确实厉害,但是不是别人想考的。
class Solution {
public:
vector<string> Permutation(string str) {
vector<string> result;
if (str.size() == 0) return result;
sort(str.begin(), str.end()); //先按照字典排序
do {
result.push_back(str);
}while (next_permutation(str.begin(), str.end())); //得到下一个排序
return result;
}
};
4. 本题小结
- 大多数思想是使用递归,后面的再细看吧。