目录
代码存放地址:https://github.com/Xke1718He/C-Practice
3.反转从位置 m 到 n 的链表(leetcode 92)
代码存放地址:https://github.com/Xke1718He/C-Practice
1.最大公约数和最小公倍数
分析:辗转相除法
#include <iostream>
using namespace std;
//最大公约数
int gcd(int num1,int num2)
{
int t=num1%num2;
while(t!=0)
{
num1=num2;
num2=t;
t=num1%num2;
}
return num2;
}
//最小公倍数
int lcm(int num1,int num2)
{
return num1*num2/gcd(num1,num2);
}
int main()
{
int num1,num2;
cin>>num1>>num2;
int num_gcd=gcd(num1,num2);
cout<<"最大公约数: "<<num_gcd<<endl;
int num_lcm=lcm(num1,num2);
cout<<"最小公倍数: "<<num_lcm<<endl;
return 0;
}
输出结果:
2.判断素数
分析:
素数的定义:一个数如果只能被1和本身嗯整除,那么这个数就是素数。
注意:
判断素数的时候,循环的区间是[2,num).
时间复杂度:O(n^2)
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
bool isPrime(int num)
{
for(int i=2;i<num;i++)
{
if(num%i==0)
return false;
}
return true;
}
int main ()
{
int n;
cin>>n;
for(int j=2;j<n;j++)
{
if(isPrime(j))
{
cout<<j;
cout<<" ";
}
}
return 0;
}
改进版:
遍历区间从[2,num)到[2,sqrt(num)]
for(int i=2;i<num;i++)
变为:
for(int i=2;i*i<=num;i++)
输出结果:
3.计算字符串的ASCII码
题目描述:
编写一个程序,在终端输入一个字符,输出它的ASCII码。
分析:
一个字符在内存中存放的形式是以它的ASCII码形式存放的,大小为8位(bit),一个字节,例如:空格键的ASCII码为32,内存中32对应的8位二进制数100000.因此,只需变换输出的类型。
#include<iostream>
#include<stdlib.h>
using namespace std;
int main()
{
char c;
printf("please input a character\n");
scanf("%c",&c);
printf("ASCII: %d",c);
return 0;
}
4. 字符类型统计器
题目描述:
请输入一个C程序,在终端用键盘输入字符串,以Ctrl+Z组合键表示输入完毕,统计输入的字符串中空格符、制表符、换行符的个数,并显示统计的结果。
#include<iostream>
using namespace std;
int main()
{
int space=0,table=0,enter=0;
char c;
printf("please input a string: \n");
while(scanf("%c",&c)&&c!='q')
{
switch (c)
{
case 32:
space++;
break;
case 9:
table++;
break;
case 10:
enter++;
break;
default:
break;
}
}
printf("The number of space: %d\n",space);
printf("The number of table: %d\n",table);
printf("The number of enter: %d\n",enter);
return 0;
}
5.三位数反转
题目描述:输入一个三位数,分离出2它的百位、十位、个位,反转后输出
分析:
百位:num/100;
十位:(num%100)/10;
个位:num%10;
#include <iostream>
using namespace std;
int main()
{
int num,NewNum;
printf("请输入一个三位数: \n");
cin>>num;
NewNum=num/100+((num%100)/10)*10+(num%10)*100;
cout<<"翻转结果为: "<<NewNum<<endl;
return 0;
}
6.交换变量
题目描述:
输入两个整数a和b,交换二者的值后输出。
方法一: 借助中间变量
#include <iostream>
using namespace std;
void swap(int &a,int &b)
{
int t;
t=a;
a=b;
b=t;
}
int main()
{
int a,b;
printf("请输入两个整数: \n");
cin>>a>>b;
swap(a,b);
cout<<"结果为:"<<a<<" "<<b;
return 0;
}
方法二: 不借助中间变量
#include <iostream>
using namespace std;
void swap1(int &a,int &b)
{
int t;
t=a;
a=b;
b=t;
}
void swap2(int &a,int &b)
{
a= a+b;
b= a-b;
a= a-b;
}
int main()
{
int a,b;
printf("请输入两个整数: \n");
cin>>a>>b;
swap1(a,b);
cout<<"结果1为:"<<a<<" "<<b<<endl;
swap2(a,b);
cout<<"结果2为:"<<a<<" "<<b<<endl;
return 0;
}
方法三: 位运算
void swap3(int &a,int &b)
{
a=a^b;
b=b^a;
a=a^b;
}
7.鸡兔同笼
题目描述:
已知鸡和兔的总数量为n,总腿数为m。输入n和m,依次输出鸡的数目和兔的数目。
分析:
鸡兔的数量不能为0,其次腿数为偶数,解方程:
m=2*chicken+4*rabbit;
n= chicken+ rabbit;
#include <iostream>
using namespace std;
int main()
{
int n,m;
int chicken,rabbit;
printf("请输入总数量和总腿数: \n");
cin>>n>>m;
chicken=(4*n-m)/2;
rabbit=(m-2*n)/2;
if(chicken<0||rabbit<0||m%2==1)
{
cout<<"NO answer"<<endl;
}
else
cout<<"鸡:"<<chicken<<" 兔: "<<rabbit<<endl;
return 0;
}
8.三整数排序
题目描述:
输入三个整数,从小到大排序后输出。
#include<iostream>
using namespace std;
int main()
{
int num1,num2,num3;
cin>>num1>>num2>>num3;
int t;
if(num1>num2)
{
t=num1;
num1=num2;
num2=t;
}
else if(num2>num3)
{
t=num2;
num2=num3;
num3=t;
}
else if(num1>num3)
{
t=num1;
num1=num3;
num3=t;
}
cout<<num1<<" "<<num2<<" "<<num3<<endl;
return 0;
}
9.最长回文字符串
给定一个字符串s,找到s中最长的回文字符串。
示例:
输入:“babad”
输出:“bab”
注意:“aba”也是有效答案。
分析:
std::string::substr(pos,len)
返回一个子字符串,如果等于字符串的长度,则返回一个空字符串,如果超出字符串的长度,则返回out_of_range异常
- 为什么要传入两个值l和r,因为可以同时处理回文字符串为奇数和偶数的情况。
- 时间复杂度为:O(N^2)
- 空间复杂度为:O(1)
#include <iostream>
#include <string>
using namespace std;
string Palindrome(string &s,int l,int r)
{
while((l>=0)&&(r<s.size()))
{
if(s[l]!=s[r])
break;
l--;
r++;
}
return s.substr(l+1,r-l-1);
}
int main()
{
string s;
printf("请输入一个字符串: \n");
cin>>s;
string news1,news2,res;
for(int i=0;i<s.size();i++)
{
news1=Palindrome(s,i,i);
news2=Palindrome(s,i,i+1);
res=res.size()>news1.size()?res:news1;
res=res.size()>news2.size()?res:news2;
}
cout<<res<<endl;
return 0;
}
10.有序数组去重
题目描述:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(1)额外空间的条件下完成。
#include <iostream>
using namespace std;
int removeDuplicates(int num[],int length)
{
int slow,fast;
slow=0,fast=1;
while(fast<length)
{
if(num[slow]==num[fast])
{
fast++;
}
else
{
slow++;
num[slow]=num[fast];
fast++;
}
}
return slow+1;
}
int main()
{
int n;
printf("输入有序数组的长度:");
cin>>n;
int num[n];
for(int i=0;i<n;i++)
{
cin>>num[i];
}
n=removeDuplicates(num,n);
for(int i=0;i<n;i++)
{
cout<<num[i]<<" ";
}
return 0;
}
11.年份
题目描述:
输入年份,判断是否为闰年。如果是,则输出yes,否则输出no.
#include <iostream>
using namespace std;
bool isLeapYear(int num)
{
if((num%4==0)&&(num%100!=0)||(num%400==0))
return true;
else
return false;
}
int main()
{
int year;
printf("请输出年份: \n");
cin>>year;
if(isLeapYear(year))
printf("是");
else
printf("否");
return 0;
}
12.x的平方根
二分查找
基本思想:
问题:
int mid=(left+right)/2;
在left和right都比较大的时候,left+right很有可能超过int类型能表示的最大值,即整型溢出,为了避免这个问题,应该写成:
int mid=left+(right-left)/2;
事实上,
int mid = left + (right - left) / 2
在right
很大、left
是负数且很小的时候,right - left
也有可能超过int
类型能表示的最大值,只不过一般情况下left
和right
表示的是数组索引值,left
是非负数,因此right - left
溢出的可能性很小。更好的写法:
int mid=(left+right)>>>1;
#include <iostream>
using namespace std;
int x_sqrt(int num)
{
int left=0;
int right=num;
int ans;
while(left<right)
{
int mid=left+(right-left)/2;
if(num/mid>=mid)
left=mid+1;
else
right=mid;
}
return right-1;
}
int main()
{
cout<<"请输入一个数: "<<endl;
int num1,num2;
cin>>num1;
num2=x_sqrt(num1);
cout<<"结果为: "<<num2<<endl;
return 0;
}
错误版本 :
#include <iostream>
using namespace std;
int x_sqrt(int num)
{
int left=1;
int right=num;
int ans;
while(left<=right)
{
int mid=left+(right-left)/2;
ans=mid*mid;
if(ans==num)
{
return mid;
}
if(ans>num)
{
right=mid-1;
}
else
{
left=mid+1;
}
}
}
int main()
{
cout<<"请输入一个数: "<<endl;
int num1,num2;
cin>>num1;
num2=x_sqrt(num1);
cout<<"结果为: "<<num2<<endl;
return 0;
}
13.接雨水
题目描述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
分析:
#include <iostream>
#include <algorithm>
using namespace std;
int trap(int num[],int n)
{
int sum=0;
for(int i=0;i<n;i++)
{
int max_left=0;
for(int j=i-1;j>=0;j--)
{
if(num[j]>max_left)
max_left=num[j];
}
int max_right=0;
for(int j=i+1;j<n;j++)
{
if(num[j]>max_right)
max_right=num[j];
}
int min1= min(max_left,max_right);
if(min1>num[i])
{
sum+=(min1-num[i]);
}
}
return sum;
}
int main()
{
cout<<"请输入柱子的个数: \n";
int n;
cin>>n;
int num[n];
for(int i=0;i<n;i++)
{
cin>>num[i];
}
int trap_num;
trap_num=trap(num,n);
cout<<"总和为: "<<trap_num<<endl;
return 0;
}
分析:从两端往中间逼近,记录左右两端高度最高值,那么对于这两端最高值中间部分,如果高度低于两端最高值,能接的雨水取决于两端最高值中的最小值。
int trap(int height[],int n)
{
int left=0,right=n-1;
int lefth=0,righth=0,area=0;
while(left<right)
{
if(height[left]<height[right])
{
if(lefth<=height[left])
lefth=height[left];
else
area+=lefth-height[left];
left++;
}
else
{
if(righth<=height[right])
righth=height[right];
else
area+=righth-height[right];
right--;
}
}
return area;
}
14.矩阵的转置
题目描述:
从键盘输入一个n*m的矩阵,实现矩阵的转置输出。
#include <iostream>
using namespace std;
int main()
{
int n,m;
cout<<"请输入矩阵的行和列: \n";
cin>>n>>m;
int matrix[n][m];
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cin>>matrix[i][j];
}
}
cout<<"原矩阵: \n";
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cout<<matrix[i][j]<<" ";
}
cout<<endl;
}
cout<<"矩阵的转置: \n";
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
cout<<matrix[j][i]<<" ";
}
cout<<endl;
}
return 0;
}
15.十进制和二进制转换器
题目描述:
编写一个程序,将输入的十进制数转换为二进制表示。
#include <iostream>
using namespace std;
void deTobi(int num,int stack[])
{
int i=0;
int t=num/2;
int s=num%2;
do{
t=num/2;
s=num%2;
stack[i]=s;
if(t!=0)
{
num=t;
i++;
}
}while(t);
cout<<"结果为: "<<endl;
for(;i>=0;i--)
{
cout<<stack[i];
}
}
int main()
{
int stack[10];
int num;
cout<<"请输入一个数字: "<<endl;
cin>>num;
deTobi(num,stack);
return 0;
}
16. 数组倒序
#include<iostream>
using namespace std;
void reverse_1(int *arr,int l,int r)
{
if(l<r)
{
swap(arr[l],arr[r]);
return reverse_1(arr,l+1,r-1);
}
else
return;
}
void reverse_2(int *arr,int l,int r)
{
while(l<r)
{
swap(arr[l],arr[r]);
l++;
r--;
}
}
int main()
{
int arr[5]={0,5,6,7,8};
for(int i=0;i<5;i++)
std::cout<<arr[i]<<" ";
std::cout<<std::endl;
reverse_2(arr,0,4);
for(int i=0;i<5;i++)
std::cout<<arr[i]<<" ";
std::cout<<std::endl;
return 0;
}
17. 求区间的最大和第二大元素
#include<iostream>
using namespace std;
void max_and_second_max_1(int *arr,int l,int r,int &maxIndex,int &second_maxIndex)
{
//得到最大的元素
maxIndex=l;
for(int i=l+1;i<=r;i++)
{
if(arr[maxIndex]<arr[i])
{
maxIndex=i;
}
}
//在[l,maxIndex-1][maxIndex+1,r]区间找到次大元素
second_maxIndex=l;
for(int i=l+1;i<maxIndex;i++)
{
if(arr[i]>arr[second_maxIndex])
second_maxIndex=i;
}
for(int i=maxIndex+1;i<=r;i++)
{
if(arr[i]>arr[second_maxIndex])
second_maxIndex=i;
}
}
void max_and_second_max_2(int *arr,int l,int r,int &maxIndex,int &second_maxIndex)
{
if(arr[l]>arr[l+1])
swap(arr[l],arr[l+1]);
for(int i=l+2;i<=r;i++)
{
if(arr[i]>arr[l])
{
if(arr[i]>arr[l+1])
swap(arr[i],arr[l+1]);
else
swap(arr[i],arr[l]);
}
}
maxIndex=arr[l+1];
second_maxIndex=arr[l];
}
int main()
{
int n;
std::cout<<"please input a number: "<<std::endl;
cin>>n;
int arr[n];
std::cout<<"please input array: "<<std::endl;
for(int i=0;i<n;i++)
cin>>arr[i];
int max, second_max;
max_and_second_max_2(arr,0,n-1,max,second_max);
std::cout<<"second max: "<<std::endl;
std::cout<<max<<" "<<second_max<<std::endl;
return 0;
}
1. 买卖股票最佳时机(leetcode 121)
- 题目描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
- 示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
- 示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0
- 分析:
该题目分为两种操作:买入和卖出,而且买入在前,卖出在后,利润:卖出-买入。因此,通过遍历整个价格,同时倒序遍历价格,当买入和卖出相等时,结束,将此时的利润和前面的最大利润进行比较,取最大值。
- 改进1:
使用 两个指针min和max记录买入的最小、当轮卖出的最大价格,其余情况跳过。(但是没啥效果,哈哈哈)
- 改进2:
如果我是在历史最低点买的股票就好了!太好了,在题目中,我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice。(分析来源于leetcode)
- 方法一:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int max_profit=INT_MIN;
//i表示买入时候的价格
//j表示卖出时候的价格
for(int i=0;i<prices.size();i++)
{
for(int j=prices.size()-1;j>i;j--)
{
int sum=prices[i]*(-1)+prices[j];
max_profit=max(sum,max_profit);
}
}
return max_profit<0?0:max_profit;
}
};
- 方法二:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int max_profit=INT_MIN;
//i表示买入时候的价格
//j表示卖出时候的价格
int min_price=INT_MAX;
for(int i=0;i<prices.size();i++)
{
if(prices[i]>min_price)
continue;
min_price=prices[i];
for(int j=prices.size()-1;j>i;j--)
{
int max_price=INT_MIN;
if(prices[j]<max_price)
continue;
max_price=prices[j];
int sum=prices[i]*(-1)+prices[j];
max_profit=max(sum,max_profit);
}
}
return max_profit<0?0:max_profit;
}
};
- 方法三
class Solution {
public:
int maxProfit(vector<int>& prices) {
int max_profit=0;
int min_price=INT_MAX;
for(int i=0;i<prices.size();i++)
{
max_profit=max(max_profit,prices[i]-min_price);
min_price=min(min_price,prices[i]);
}
return max_profit;
}
};
回溯法
1.全排列(leetcode 46)
- 分析:
对[1,2,3]进行全排列={取出其中一个数字}+对剩余的数字进行全排列,这也就是递归操作。
- 实现:
class Solution {
public:
vector<vector<int>> res;
vector<bool> used;
//
void permute(vector<int>&nums,int index,vector<int>&ans)
{
if(index==nums.size())
{
res.push_back(ans);
return;
}
for(int i=0;i<nums.size();i++)
{
ans.push_back(nums[i]);
permute(nums,index+1,ans);
//回溯操作
ans.pop_back();
}
return;
}
vector<vector<int>> permute(vector<int>& nums) {
vector<int> ans;
permute(nums,0,ans);
return res;
}
};
结果: [[1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3],[2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3],[3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3]]
class Solution {
public:
vector<vector<int>> res;
vector<bool> used;
void permute(vector<int>&nums,int index,vector<int>&ans)
{
if(index==nums.size())
{
res.push_back(ans);
return;
}
for(int i=0;i<nums.size();i++)
{
if(!used[i])
{
ans.push_back(nums[i]);
used[i]=true;
permute(nums,index+1,ans);
//当递归到底时,需要把插入的元素退出,状态恢复原样
ans.pop_back();
used[i]=false;
}
}
return;
}
vector<vector<int>> permute(vector<int>& nums) {
//状态变量,判断是否被使用
used=vector<bool>(nums.size(),false);
vector<int> ans;
permute(nums,0,ans);
return res;
}
};
结果:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
查找类:
1.最长回文串
题目描述:
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如
"Aa"
不能当做一个回文字符串。注意:
假设字符串的长度不会超过 1010。示例 1:
输入: "abccccdd" 输出: 7 解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
分析:
- 回文,英文palindrome,指一个顺着读和反过来读都一样的字符串,比如madam。
- 回文字符串的里面的字符都是两两配对出现,除了字符串的长度为奇数时,最中间的那个字符只出现一次。
- 这道题是让我们构建回文字符串,我们可以将其分为两部分。
- 第一部分:回文的前半部分(包括中间那个元素),比如:madam的mad;
- 第二部分:回文的后半部分(不包括中间那个元素),比如:madam的am;
- 首先对字符串进行排序,使得重复的字符连续在一起;
- 遍历整个字符串,比较当前字符和下一个字符是否为同一个字符;
- 如果是,则将其分给前半部分,后半部分各一个,然后向后移动两个位置。
- front_s=s[i]+front_s;back_s+=s[i];
- 如果不是,那么这个字符就是单着的,那么就选择第一次出现的单着的字符加到前半部分上去,其余的舍去,然后向后移动一个位置。
- front_s=s[i]+front_s;
- 最后前半部的长度加上后半部分的长度就是回文的总长度。
优化:
- 可以直接统计长度,不构造前半部分,后半部分字符串。
- 可以用hash表:
- 构建一个长为52的hash数组,数组每个位置对应一个字母;
- 统计字符串中每个字母出现的次数;
- 然后遍历hash数组,如果次数为偶数,则加到总长度上;
- 如果次数为奇数,减去1再加到总长度上。
class Solution {
public:
int longestPalindrome(string s) {
if(s.empty())
return 0;
//先排序,重复的字符分布在一起
sort(s.begin(),s.end());
//新的字符串的前半部分
string front_s="";
//新的字符串的后半部分
string back_s="";
bool flag=true;
for(int i=0;i<s.length();)
{
//两两配对
if(i<(s.length()-1)&&s[i]==s[i+1])
{
front_s=s[i]+front_s;
back_s+=s[i];
i=i+2;
}
else
{
if(flag)
{
front_s=s[i]+front_s;
flag=false;
i++;
}
else
{
i++;
}
}
}
// cout<<front_s+back_s<<endl;
return front_s.length()+back_s.length();
}
};
- 优化版本:
class Solution {
public:
int longestPalindrome(string s) {
if(s.empty())
return 0;
//先排序,重复的字符分布在一起
sort(s.begin(),s.end());
int num=0;
bool flag=true;
for(int i=0;i<s.length();)
{
//两两配对
if(i<(s.length()-1)&&s[i]==s[i+1])
{
num+=2;
i=i+2;
}
else
{
if(flag)
{
num++;
flag=false;
i++;
}
else
{
i++;
}
}
}
return num;
}
};