41.和为S的连续正数序列
思路一:
由于要找的是和为S的连续正数序列,那么这个序列是个公差为1的等差数列,而这个序列的中间值带变了平均值的大小,假设序列的长度为n,则中间值可以通过(s/n)得到.满足条件的n分为两种情况:
- n为奇数时,序列中间的数正好是平均值,所以条件为(n&1) == 1&&sum%n ==0.(奇数的二进制最右的一位数字一定为1,(n&1) ==1用于判断是否是奇数,此时序列中间数正好是平均值,中间数能被sum整除,所以sum%n ==0.)
- n为偶数时,序列中间两个数的平均值为序列的平均值,而这个平均值的小数部分为0.5,所以条件为(sum%n)*2 == n.(例如4,5,6,7这四个数的和是22,平均值是5.5(小数部分为0.5,说明余数是除数的一半))
由题可知n>=2,那么n的最大值是多少呢?
等差数列求和公式:
S
=
(
1
+
n
)
∗
n
/
2
S = (1+n)*n/2
S=(1+n)∗n/2可得n<根号下2S.(对不起,没找到根号)
例如输入sum%100,我们只需要遍历 n = 13~2的情况,n = 8时得到序列[9,10,11,12,13,14,15,16],当n=5时,得到序列[18,19,20,21,22]
package com.matajie;
import java.util.ArrayList;
/**
* 41.和为S的连续正数序列
* 题目描述
* 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。
* 但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。
* 没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,
* 你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class FindContinuousSequence {
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>();
for(int n = (int) Math.sqrt(2*sum); n>=2;n--){
if((n&1) == 1 && sum%n == 0|| (sum%n)*2 == n){
ArrayList<Integer> list = new ArrayList<>();
for(int j = 0,k = (sum/n)-(n-1)/2;j<n;j++,k++){//sum/n时序列的中间值,往前推(n-1)/2就是起始值
list.add(k);
}
arrayLists.add(list);
}
}
return arrayLists;
}
}
思路二:双指针(类似滑动窗口)
当总和小于sum,大指针继续+,否组小指针继续+.
package com.matajie;
import java.util.ArrayList;
/**
* 41.和为S的连续正数序列
* 题目描述
* 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。
* 但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。
* 没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,
* 你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class FindContinuousSequence {
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
int low = 1;//两个起点,相当于动态窗口的两边,根据其窗口内的值之和确定窗口的位置和大小
int high = 2;
while (high>low){
int cur = (high+low) * (high-low+1)/2;//由于是连续的,差为1的序列,那么求和公式是(a0+an)*n/2.
if(cur == sum){//相等,那么就将窗口范围的所有数添加进结果集
ArrayList<Integer> list = new ArrayList<>();
for(int i = low;i<=high;i++){
list.add(i);
}
result.add(list);
low++;
}else if(cur < sum){//当前窗口内的值之和小于sum,那么右边窗口右移一个
high++;
}else {//当前窗口内的值之和大于sum,那么左边窗口右移一个
low++;
}
}
return result;
}
}
42.和为S的两个数字
题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
思路:
找到第一组数(相差最大的)就是乘积最小的.
证明: x + y = c(c为常数)
不妨设y >= x,
y - x = d >= 0,
即 y = x + d,
2x + d = c,
x = (c-d)/2,
x * y = x (x + d)
=(c - d)(c + d)/4
=(c2-d2)/4
关于d的二次函数是开口朝下的,因为d>=0,所以d最大,x*y越小
package com.matajie;
import java.util.ArrayList;
/**
* 42.和为S的两个数字
* 题目描述
* 输入一个递增排序的数组和一个数字S,在数组中查找两个数,
* 使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class FindNumbersWithSum {
public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
ArrayList<Integer> res = new ArrayList<>();
int i = 0;
int j = array.length-1;
while (i<j){
if(array[i] + array[j] == sum){
res.add(array[i]);
res.add(array[j]);
return res;
}
if(array[i] + array[j]<sum){
i++;
}else {
j--;
}
}
return res;
}
}
43.左旋转字符串
题目描述
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
思路一:
package com.matajie;
/**
* 43.左旋转字符串
* 题目描述
* 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,
* 就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。
* 例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class LeftRotateString {
public String LeftRotateString(String str,int n) {
int len = str.length();
if(len == 0){
return "";
} n%=len;
str+=str;
return str.substring(n,len+n);
}
}
思路二:
三部反转:
- 反转字符串的前半段
- 反转字符串的后半段
- 反转整个字符串
package com.matajie;
/**
* 43.左旋转字符串
* 题目描述
* 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,
* 就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。
* 例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class LeftRotateString {
public String LeftRotateString(String str,int n) {
char[] chars = str.toCharArray();
if(chars.length == 0) return "";
if(n > chars.length) n = n % chars.length;
reverse(chars,0,n-1);
reverse(chars,n,chars.length-1);
reverse(chars,0,chars.length-1);
return new String(chars);
}
public void reverse(char[] chars,int start,int end){
while (start < end){
char temp = chars[start];
chars[start] = chars[end];
chars[end] = temp;
start++;
end--;
}
}
}
44.翻转单词顺序列
题目描述
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student.a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
思路一:
翻转“I am a student.”中所有的字符得".tneduts a ma I",此时不但翻转了句子中单词的顺序,连单词内的字符顺序也被翻转了,下一步再翻转每个单词中字符的顺序.
思路二:
我们也可以一个一个字母处理.从前往后一直读取,遇到空格之后就把之前读取到的压到结果前面并添加空格.
最后当循环结束,如果用来读取的字符串不为空,那么就再把它压到结果前,这次就不用在结果的最前面加空格了
思路三:
我们也可以直接使用String进行拼接
package com.matajie;
/**
* 44.翻转单词顺序列
*
* 题目描述
* 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。
* 同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,
* 但却读不懂它的意思。例如,“student. a am I”。后来才意识到,
* 这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。
* Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class ReverseSentence {
public String ReverseSentence(String str) {
if(str.trim().equals("")){
return str;
}
String[] ts = str.split(" ");
String newStr = new String();
for(int i = ts.length-1;i>0;i--){
newStr += ts[i] + " ";
}
return newStr+ts[0];
}
}
扩展:翻转hello my name is Jack ->Jack is name my hello.
思路:
- 首先将String转换成字符数组.
- 再将字符串头尾互换,将整个句子翻转,同思路一.
- 循环遍历获取每个单词的头尾索引值,并嵌套一个循环进行单词翻转.
package com.matajie;
/**
* 翻转hello my name is Jack ->Jack is name my hello.
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class ReverseSentence {
public String ReverseSentence(String str) {
char[] chars = str.toCharArray();
int len = chars.length;
for(int i = 0;i<len/2;i++){
chars[len-1-i] = (char) (chars[len-1-i]^chars[i]);//句子头尾互换,进行len/2次交互
chars[i] = (char) (chars[len-1-i]^chars[i]);
chars[len-1-i] = (char) (chars[len-1-i]^chars[i]);
}
int start = -1;//每个单词的起始索引
int end = -1;//每个单词的结束索引
for(int i = 0;i<len;i++){
if(start == -1 && chars[i] != ' '){
start = i;
}
if(chars[i] == ' '){//当前索引值为空时,单词结束索引为上一个索引值,这里不用考虑上一个是否还是空格,
end = i - 1;
}else if(i == len - 1){//如果是最后一个索引值时,单词结束索引就是结尾字符
end = len - 1;
}
if(end >= 0 && chars[end] != ' '){//判断单词结束所in指符合要求,并且对应的字符不是空字符,则进行单词头尾交换.
int wordLen = end - start;
for(int j = 0;j <= wordLen/2;j++){//单词头尾交换
int first = start + j;
int last = start + wordLen -j;
if(chars[first] == chars[last]){//如果字符相同则不必交换
continue;
}
chars[first] = (char) (chars[first]^chars[last]);
chars[last] = (char) (chars[first]^chars[last]);
chars[first] = (char) (chars[first]^chars[last]);
start = -1;end = -1;
}
}
}
return new String(chars);
}
}
解释上述代码片段:
现有A,B两值需要交互,可写为:
- A = A^B;
- B = A^B;
A = A^B;
将1带入2得B = (A^B )^B,
即B = A^(B ^ B),自己异或自己为0,任何值异或0为其本身,
所以B = A^0 = A.
完成交互.
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。
思路一:
package com.matajie;
import java.util.Arrays;
/**
* 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。
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class IsContinuous {
public boolean isContinuous(int [] numbers) {
if(numbers.length < 5){
return false;
}
int low = 0;
Arrays.sort(numbers);
while (numbers[low] == 0){
low++;
}
for(int i = low;i<numbers.length-1;i++){
if(numbers[i] == numbers[i+1])
return false;
}
return numbers[numbers.length-1]-numbers[low] <= 4 ? true : false;
}
}
思路二:
要组成顺子,需满足1.除0外没有重复的数(0表示大王小王,) 2. max - min < 5.
package com.matajie;
/**
* 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。
*
* 我的程序才不会有bug!
* author:年仅18岁的天才少年程序员丶mata杰
**/
public class IsContinuous {
public boolean isContinuous(int [] numbers) {
if(numbers.length != 5){
return false;
}
int min = 14;//牌组大小在0~13之间,min初始值要大于牌组值,
int max = -1;//max初始值要小于牌组值
int flag = 0;
for(int i = 0;i<numbers.length;i++){
int number = numbers[i];//将数组做标记改用bit做标记,时间空间都节省
if(number<0 || number > 13){
return false;
}
if(number == 0){
continue;
}
if(((flag>>number) & 1) == 1){//用二进制来判断是否由数字重复Bit-map
return false;
}
flag |= (1<<number);
if(number>max){
max = number;
}
if(number<min){
min = number;
}
if(max - min >= 5){
return false;
}
}
return true;
}
}
解释:
用二进制位来判断是否有数字重复Bit-map
假设我们要对0-7内的5个元素(4,7,2,5,3)排序(假设元素无重复),我们采用Bitmap的方法达到排序的目的,要表示8个数,我们需要8个bit(1Bytes),首先开辟1Bute的空间,将空间的所有Bit位都置为0,然后遍历这5个元素,首先第一个元素为4,那就把4对应的位 置为1(可以这样操作:
p+(i/8)|0x01<(i%8)),当然了这里的操作涉及到Big-ending和little-ending的情况,这里默认Big-ending)
因为从0开始,所以把第五位 置为1.
然后在处理第二个元素7,将第八位 置为1,…一直处理完所有元素将相应的位 置为1.然后遍历一遍Bit区域,将该位是1的位的编号输出(2,3,4,5,7),这样就达到了排序的目的.
扩展:Bitmap和2-Bitmap海量数据处理问题
Bitmap:一个byte占8个bit,可用bit的0或1代表某数组值有或没有,0代表没有出现过,1代表该数组值出现过.
怎么快速定位它的索引呢?Index(N)代表N的索引号,Position(N)代表N的位置号.
公式:Index(N) = N/8 = N >> 3;
Position(N) = N%8 = N&7/
add(int num)向Bitmap里add数据怎么办?
public void add(int num)
{
int index = num >> 3;
int position = num &7;
byte[index] |= 1<<position;//做或操作
}
2Bitmap:每个数分配2bit,00表示不存在,01表示出现1次,10表示多次,其它同Bitmap.
一般解决问题:
问题1.在2.5亿个整数中找出不重复的整数,内存不足以容纳2.5亿个整数
问题2.在40亿个不重复的unsigned int 的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数中.
缺点:
- 可读性差
- 存储的元素个数 <= 元素的最大值,受限于存储空间大小.(比如要存储值为65535的数,就必须要65535/8 = 8k字节的内存,这就导致了位图法不合适,存unsigned int类型的数大约需要232/8 = 5亿字节的内存)(Java没有unsigned int)
- 位图对有符号类型数据的存储需要2位来表示一个有符号元素