剑指Offer系列之题41~题45
41.数组中只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
暴力解:利用HashMap存储数字出现次数;
依次异或:先进行依次异或得到两个出现一次数字的异或结果,根据该结果进行分组,然后每一组进行异或得到这两个数字。
1.暴力解:
//num1,num2分别为长度为1的数组。
//将num1[0],num2[0]设置为返回结果
import java.util.Map;
import java.util.HashMap;
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
//法1:先排序,然后遍历
//法2:辅助map存储
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<array.length;++i){
if(!map.containsKey(array[i])){
map.put(array[i],1);
}else{
map.put(array[i],2);
}
}
int count=0;
for(int i=0;i<array.length;++i){
if(map.get(array[i])==1){
if(count==0){
num1[0]=array[i];
count++;
}else{
num2[0]=array[i];
}
}
}
}
}
2.依次异或:
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
import java.util.Map;
import java.util.HashMap;
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
//方法1:先排序,然后遍历
//方法2:辅助map存储
//方法3:依次异或:异或的结果中,相同数字全部抵消。得到的结果是两个出现一次的数字异或的结果。
//其中至少有一位是1,即两数字这一位不同,然后根据该位是否为1将全部数分为两组,
//然后每一组的异或结果就是只出现一次的数
int res=0;
for(int i=0;i<array.length;++i){
res^=array[i];
}
int n=0;
while((res & 1) !=1){//未找到1
res=res>>1;
n++;//第n位是1
}
for(int i=0;i<array.length;++i){
if((array[i]>>n & 1)!=1){//若该位不是1
num1[0]^=array[i];
}else{
num2[0]^=array[i];
}
}
}
}
42.和为S的连续正数序列 🔺
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述
:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
双指针:左右指针从头开始(1,2),当大于和时左指针右移,小于和时,右指针右移。都向右移是为了查找所有序列
双指针:
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
//连续的 从小到大 至少两个数
ArrayList<ArrayList<Integer>> resList=new ArrayList<>();
int low=1;//左右指针,两者都只能右移
int high=2;
while(high>low){
int cur=(low+high)*(high-low+1)/2;//当前序列的和
if(cur==sum){
//若相等则加入列表
ArrayList<Integer> res=new ArrayList<>();
for(int i=low;i<=high;++i){
res.add(i);
}
resList.add(res);
//添加后移动左指针继续查找下一个序列
low++;
}else if(cur>sum){//若大于和,则将左指针右移,减小序列的和
low++;
}else if(cur<sum){//若小于和,则将右指针右移,增大序列的和
high--;
}
}
return resList;
}
}
43.和为S的两个数字
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输入描述
:对应每个测试案例,输出两个数,小的先输出。
双指针,分别从前后开始遍历,大于和时右指针左移,小于和时左指针右移,第一次等于和时,即是乘积最小。
1.双指针:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
//递增排序 可能存在相等的情况
int product=Integer.MAX_VALUE;//乘积
int low=0;
int high=array.length-1;
ArrayList<Integer> res=new ArrayList<>();
if(array.length<=1)
return res;
while(low<high){//从头和尾开始遍历,当大于和时,右指针左移,小于和时,左指针右移
int cur=array[low]+array[high];
if(cur==sum){
if((array[low]*array[high])<product){
if(!res.isEmpty()){//若列表非空则先清空列表
res.remove(1);
res.remove(0);
}
res.add(array[low]);
res.add(array[high]);
product=array[low]*array[high];
}
low++;
}else if(cur<sum){
low++;
}else{
high--;
}
}
return res;
}
}
2.简化版:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
//递增排序 可能存在相等的情况
int low=0;
int high=array.length-1;
ArrayList<Integer> res=new ArrayList<>();
if(array.length<=1)
return res;
while(low<high){//从头和尾开始遍历,当大于和时,右指针左移,小于和时,左指针右移
int cur=array[low]+array[high];
if(cur==sum){//第一对相等的就是乘积最小的
res.add(array[low]);
res.add(array[high]);
break;
}else if(cur<sum){
low++;
}else{
high--;
}
}
return res;
}
}
44.左旋转字符串
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
暴力解:利用String的substring方法;
利用翻转特性:先翻转前n个,再翻转剩余部分,然后整体翻转,可以达到效果。
1.暴力解:
public class Solution {
public String LeftRotateString(String str,int n) {
//考虑str、n的异常输入
if(n>=str.length())//移位超过字符串长度则相当于移减去字符串长度的距离
n-=str.length();
if(n<=0 || str.equals(""))
return str;
char c[]=new char[n];
for(int i=0;i<n;++i){
c[i]=str.charAt(i);
}
String temp="";
for(int i=0;i<n;++i){
temp+=c[i];
}
str=str.substring(n,str.length());//取要前移的子字符串
return str+temp;
}
}
2.简化:
public class Solution {
public String LeftRotateString(String str,int n) {
//考虑str、n的异常输入
if(n<=0 || str.equals(""))
return str;
if(n>=str.length())//移位超过字符串长度则相当于移减去字符串长度的距离
n%=str.length();
int len=str.length();
str+=str;
return str.substring(n,len+n);
}
}
3.利用翻转特性:
public class Solution {
public String LeftRotateString(String str,int n) {
//考虑str、n的异常输入
if(n<=0 || str.equals(""))
return str;
if(n>=str.length())//移位超过字符串长度则相当于移减去字符串长度的距离
n%=str.length();
char c[]=str.toCharArray();
reverse(c,0,n-1);
reverse(c,n,c.length-1);
reverse(c,0,c.length-1);
return new String(c);
}
public void reverse(char []c,int start,int end){
while(start<end){
char temp=c[start];
c[start]=c[end];
c[end]=temp;
start++;
end--;
}
}
}
45.翻转单词顺序
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
从头和尾依次交换;
或者利用StringBuilder/Buffer从尾到头添加字符串;
先翻转整个字符串,然后以空格为分割翻转单词
1.依次交换:
public class Solution {
public String ReverseSentence(String str) {
if(str.trim().equals(""))
return str;
String s[]=str.split(" ");
int start=0;
int end=s.length-1;
while(start<end){
String temp=s[start];
s[start]=s[end];
s[end]=temp;
start++;
end--;
}
String res="";
for(int i=0;i<s.length;++i){//拼接
res+=s[i];
if(i!=s.length-1)
res+=" ";
}
return res;
}
}
2.整体翻转后以空格为分割翻转每个单词:
public class Solution {
public String ReverseSentence(String str) {
if(str.trim().equals(""))
return str;
char c[]=str.toCharArray();
//先翻转整个字符串,然后以空格为界,单独翻转每个单词
reverse(c,0,c.length-1);
int firBlank=-1;
for(int i =0;i<c.length;++i){
if(c[i]==' '){//找到空格
int nextBlank=i;
reverse(c,firBlank+1,nextBlank-1);//翻转空格前的单词
firBlank=nextBlank;
}
}
reverse(c,firBlank+1,c.length-1);//翻转最后一个单词
return new String(c);
}
public void reverse(char []c,int start,int end){
while(start<end){
char temp=c[start];
c[start]=c[end];
c[end]=temp;
start++;
end--;
}
}
}
如有错误,欢迎指正