文章目录

  • ​​41 和为S的连续正数序列​​
  • ​​42 和为S的两个数字​​
  • ​​43 左旋转字符串​​
  • ​​44 翻转单词顺序列​​
  • ​​45 扑克牌顺序​​
  • ​​46 孩子们的游戏​​
  • ​​47 求1+2+。。。+n​​
  • ​​48 不用加减乘除做加法★​​
  • ​​49 把字符串转换成整数​​
  • ​​50 数组中重复的数字​​
欢迎关注个人​​数据结构专栏​​哈
​​剑指offer(1-10题)详解​​剑指offer(11-25题)详解
剑指offer(26-33题)详解
剑指offer(34-40题)详解
剑指offer(41-50题)详解
剑指offer(51-59题)详解
剑指offer(60-67题)详解
微信公众号:​​bigsai​

剑指offer(41-50题)详解_数据结构


声明:大部分题基本未参考题解,基本为个人想法,如果由效率太低的或者错误还请指正!

41 和为S的连续正数序列

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

思路
这题几个要求:大于等于两个序列,正整数,连续。至于对list长度返回的要求直接重写个排序接口即可。

所以这题笔者大概想到​​2个思路​​​:
方法一:动态穷举试探(较为简单)
这个穷举并不是所谓的暴力遍历,不从每个数都进行循环试起。如果从每个数都试起那么整个进行的​​​次数太多​​。我们用一个类似拉链的动态滑动试探。我们都知道:连续相加和相等,如果数很小那么就很长,如果数很大就很短

  • 所以我们用​​left​​​和​​right​​标记左右,用一个value记录这个区间的和。
  • 如果value大了那就左面往右试探一位,如果value小了那么right就往右加变长一位。
  • 重复上述操作。一直到中间两位长度的截至,记录所有满足题意的条件。

剑指offer(41-50题)详解_i++_02


法一的ac的代码为:

import java.util.*;
public class Solution {
public static ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum)
{
ArrayList<ArrayList<Integer> > list=new ArrayList<ArrayList<Integer>>();
int left=1,right=2,value=3;
while(left<=sum/2)
{
if(value==sum)
{
ArrayList<Integer>list1=new ArrayList<Integer>();
for(int i=left;i<=right;i++)
{
list1.add(i);
}
list.add(list1);
value-=left;left++;
}
else if(value>sum)
{
value-=left;left++;
}

else {
right++;value+=right;
}
}
list.sort(compare);
return list;

}
static Comparator<ArrayList<Integer>>compare=new Comparator<ArrayList<Integer>>() {


public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
// TODO Auto-generated method stub
return o2.size()-o1.size();
}
};
}

方法二:数学转化
既然是连续的数字之和。那么请看下面的分析:

/**
* 分析 a + (a+1) --- + b =(a+b) * n/2 =sum 其中n是a-b的个数
* (a+b) * (b-a+1)=sum * 2
* 令a+b=m a=a b=m-a
* m*(m-2a+1)=sum * 2
* 利用求sum * 2 的因数遍历m 求出a,b若a大于0、且不为小数、a!=b即为一个序列
*/

大致分析是这样的:

  • 首先连续相加是可以转化成乘法的(有时间可以了解唯一分解定理)
  • 然后比如 sum * 2 =a * b 可能其中a是平均数,也可能b是平均数。但是你需要考虑所有和都大于0. 且为整数。
  • 比如 求和为15.右​​[1, 2, 3, 4, 5], [4, 5, 6], [7, 8]​​​三组数据。而求和分别为​​((1+5)/2)*5=15​​​=>​​6*5=30​​​,​​((4+6)/2)*3=15​​​=>​​10*3=30​​​,​​((7+8)/2)*2=15​​​=>​​15*2=30​​​.其实你就是需要将所有​​a*b=30​​所有情况列举,然后看看能否复原成功。复原公式在上面的。并且其中a,b需要换位考虑能否成功的。当然求因数的一些技巧不需要详细讲述了,只需要​​遍历根号(n)+1​​即可。

具体代码为:

import java.util.*;
public class Solution {
public static ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
int value=sum*2;
ArrayList<ArrayList<Integer> > list=new ArrayList<ArrayList<Integer>>();
int a=0,b=0;
for(int i=1;i*i<value+1;i++)
{
if(value%i==0)
{

a=(i+1-value/i)/2;b=i-a;
if(a>0&&i-a*2+1==value/i&&b>a)
{
ArrayList<Integer>list2=new ArrayList<Integer>();
for(int j=a;j<=b;j++)
{
list2.add(j);
}
list.add(list2);
}
a=(value/i+1-i)/2;b=value/i-a;
if(a>0&&value/i-a*2+1==i&&b>a)
{
ArrayList<Integer>list2=new ArrayList<Integer>();
for(int j=a;j<=b;j++)
{
list2.add(j);
}
list.add(list2);
}
}
}
list.sort(compare);
return list;
}
static Comparator<ArrayList<Integer>>compare=new Comparator<ArrayList<Integer>>() {
public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
// TODO Auto-generated method stub
return o2.size()-o1.size();
}
};
}

42 和为S的两个数字

题目描述

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

输出描述

对应每个测试案例,输出两个数,小的先输出。

思路
这题和上一题的方案一思想差不多。用个左右标记位置。从两边向中间逼近。两个数相加如果大于目标,那么最右侧位置right往左移动一下,如果小于目标,左侧的left向右移动增加,当出现第一个满足条件的即可停止。因为要求​​乘积最小嘛​​。

实现代码为:

import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
int small=0,big=0;//小大两个数
int l=0, r=array.length-1;
while(l<r)
{
if(array[l]+array[r]==sum)
{
small=array[l];big=array[r];
break;
}
else if (array[l]+array[r]<sum) {
l++;
}
else if (array[l]+array[r]>sum) {
r--;
}
}
ArrayList<Integer>list=new ArrayList<Integer>();
if(small==big)return list;
list.add(small);
list.add(big);
return list;
}
}

43 左旋转字符串

题目描述

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

思路
可以看似循环链表类似的问题。但是直接求就好了。因为只旋转一次,相当于把前面一部分挪到后面就可以了。要考虑旋转的长度是否大于整个字符串的长度。

实现代码:

public class Solution {
public String LeftRotateString(String str,int n) {
if(str.equals(""))return str;
n=n%str.length();
return str.substring(n)+str.substring(0,n);
}
}

44 翻转单词顺序列

题目描述

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

思路
经典字符串处理题。字符串问题就是有很多坑点、边界问题、特殊情况等等。要考虑空串、一个单词等等情况。当然,具体处理就是字符一个个读取,遇到​​空格​​的时候特殊处理一下。处理方式可能不唯一。也可以考虑字符串分割等等。

实现代码为:

public class Solution {
public static String ReverseSentence(String str) {

String value="";
StringBuilder sBuilder=new StringBuilder();
for(int i=0;i<str.length();i++)
{
if(str.charAt(i)!=' ')
{
sBuilder.append(str.charAt(i));
}
else {
value=sBuilder.toString()+" "+value;
sBuilder=new StringBuilder();
}
}
if(sBuilder.length()>0)
{
value=sBuilder.toString()+" " +value;
value=value.substring(0,value.length()-1);
}
return value;
}
}

45 扑克牌顺序

题目描述

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

思路
首先要理清楚题意。就是给你五张牌让你判断是不是顺子,然而这五张可能有若干张​​​鬼(0)​​可以随意的陪。这也有点小贪心的思想的。我们的处理思路事这样的:

  • 先给五张牌进行排序。然后前面用个数值记录有几个鬼。
  • 然后从非鬼开始判断是否连续,如果和前一个不连续​​如果有鬼​​用鬼前一个数往后补一张一直到连续,如果没鬼了还不连续那就说明那是真的完成不了。
  • 如果最后未能遍历所有牌。或者有相同牌(非鬼)那么一定不是顺子。

实现代码为:

import java.util.Arrays;
public class Solution {
public static boolean isContinuous(int [] numbers) {
if(numbers.length<5)return false;
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
int wang=0;
int pre=0;
for(int i=0;i<5;i++)
{
if(numbers[i]==0)wang++;
else {
if(pre==0) {pre=numbers[i];}//第一个数
else if(numbers[i]==pre) {return false;}//相同牌
else if(numbers[i]==pre+1) {pre++;}
else {
while(numbers[i]>pre+1)
{
if(wang>0){wang--;pre++;}
else if(wang<=0) {return false;}
}
pre++;
}
}
}
return true;
}
}

46 孩子们的游戏

题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1

思路
经典约瑟夫环问题。可用链表进行模拟,也可用List借助数学方法一个个移出效率会高一些。笔者这里用了个链表过了。后面用数学模拟链表的方法效率更高,大过年的先不写了,后面会补的!

实现代码为:

public class Solution {
static class node
{
int val;
public node(int value) {
this.val=value;
}
node next;
}
public static int LastRemaining_Solution(int n, int m) {
if(n<=0||m<2)return -1;
node head=new node(0);
node team=head;
for(int i=1;i<n;i++)
{
team.next=new node(i);
team=team.next;
}
team.next=head;
int index=0;
while (head.next!=head) {

if(index==m-2)
{
head.next=head.next.next;
index=0;
}
else {
index++;
}
head=head.next;
}
return head.val;
}
}

至于笔者最初写的效率并不高。至于数学模拟,笔者最初记得还是分类讨论的:是否越界之类。而可以直接加上一段size长度求余相当于接上那么一段!

剑指offer(41-50题)详解_字符串_03

47 求1+2+。。。+n

题目描述

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

思路
这个真的没想到,看了下评论区的方法真的是恍然大悟。虽然我们不能用逻辑语句实现我们的逻辑,但是我们可以用​​&&​​​之类的运算符帮助我们实现逻辑。​​a&&b​​这个逻辑当a是false的时候后面就不会执行了。这就是逻辑与的短路特性。我们调用递归或者其他方法借助逻辑与的短路特性就可以实现停止了

实现代码为:

public class Solution {
public int Sum_Solution(int n) {
boolean jud= (n>0)&&((n+= Sum_Solution(n-1))>0);
return n;
}
}

​参考评论区:​​ 对于评论区还有一些其他方法也挺精妙,但是可能自己很难有那么高认识,先了解吧。

剑指offer(41-50题)详解_算法_04

48 不用加减乘除做加法★

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

思路
这个我以前就知道跟二进制有关,知道有这个东西不过没仔细研究。我觉得这题大家可以自己独立研究一下。能够加深对二进制和位运算的印象和认识

当然,做这题前,建议稍微了解下四种常用的位运算​​与或非 异或​​​。
对于二进制的运算:​​​0+0=0,0+1=1,1+1=0(进位)​

对于加法的一个二进制运算。如果不进位那么就是非常容易的。这时候相同位都为0则为0,0和1则为1.满足这种运算的​​异或(不相同取1,相同取0)​​​和​​或(有一个1则为1)​​都能满足.

剑指offer(41-50题)详解_数据结构_05


但事实那像这么容易?肯定有进位的运算啊!对于进位的两数相加,这种核心思想为

  • 用两个数,一个正常m相加(不考虑进位的)。用​​异或a^b​​就是满足这种要求,先不考虑进位(如果没进位那么就是最终结果)。
  • 另一个专门考虑进位的n。两个1需要进位。所以我们用​​a&b与​​记录需要进位的。但是还有个问题,进位的要往上面进位,所以就变成这个需要进位的数左移一位。
  • 然后就变成m+n重新开始上面直到不需要进位的(会停止的,不可能一直进位)。

剑指offer(41-50题)详解_剑指offer_06

实现代码为:

public class Solution {
public int Add(int num1,int num2) {
/*
* 5+3 5^3 5&3
* 0101 0110 0001
* 0011
*/
int a=num1^num2;
int b=num1&num2;
b=b<<1;
if(b==0)return a;
else {
return Add(a, b);
}
}
}

49 把字符串转换成整数

题目描述

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

输入描述:

输入一个字符串,包括数字字母符号,可以为空
输出描述:
如果是合法的数值表达则返回该数字,否则返回0
示例1

输入

+2147483647
1a33

输出

2147483647
0

思路
这题是个麻烦题,主要是有几个越界的字符串需要处理。你又不能使用api,建议你使用​​​long​​型转换成功判断是否越界可能更方便些。但是笔者挑战了下自己用int型硬刚过的。

你要考虑正负(整数最小和负数最大),需要考虑是否为非法字符串。数值是否操作越界(尤其注重int类型最大最小附近)

我的处理思路这样的:记录正负,每次都进行加操作,记录操作前数值,如果操作后符号变化那就越界直接返回0。否则进行到最后返回。

实现代码为:

public class Solution {
public static int StrToInt(String str) {
if(str.equals(""))return 0;
int check = 0;//判断首位是不是符号
int zhengfu = 1;//正负号用1和-1表示
if (str.charAt(0) == '+' || str.charAt(0) == '-')// 符号位判断下
{
check=1;
zhengfu=str.charAt(0)=='+'?1:-1;
}
int index = check == 0 ? 0 : 1;// 初始位置
int value = 0;
int team=zhengfu;
for (; index < str.length(); index++) {
if (str.charAt(index) >= '0' && str.charAt(index) <= '9') {
value = value * 10 + (str.charAt(index) - '0')*zhengfu;
if(value/team<0)return 0;
team=value;
//System.out.println(value+" "+zhengfu);
} else {
return 0;
}
}
return value;
}
}

50 数组中重复的数字

题目描述

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

思路
因为数组中的数范围已经确定了,也不大,不需要用hashmap。直接用一个数组即可。遍历一个加一个,这个位置的数为2返回停止。

实现代码为:

public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
int count[]=new int[length+1];
for(int i=0;i<length;i++)
{
if(++count[numbers[i]]>1)
{
duplication[0]=numbers[i];
return true;
}
}
return false;
}
}

最后欢迎大家关注公众号:​​bigsai​​长期交流、分享!

剑指offer(41-50题)详解_剑指offer_07