[Java]剑指offer41-45_day9

41.和为S的连续正数序列

思路一:

由于要找的是和为S的连续正数序列,那么这个序列是个公差为1的等差数列,而这个序列的中间值带变了平均值的大小,假设序列的长度为n,则中间值可以通过(s/n)得到.满足条件的n分为两种情况:

  1. n为奇数时,序列中间的数正好是平均值,所以条件为(n&1) == 1&&sum%n ==0.(奇数的二进制最右的一位数字一定为1,(n&1) ==1用于判断是否是奇数,此时序列中间数正好是平均值,中间数能被sum整除,所以sum%n ==0.)
  2. 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);
    }
}

思路二:

三部反转:

  1. 反转字符串的前半段
  2. 反转字符串的后半段
  3. 反转整个字符串
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.

思路:

  1. 首先将String转换成字符数组.
  2. 再将字符串头尾互换,将整个句子翻转,同思路一.
  3. 循环遍历获取每个单词的头尾索引值,并嵌套一个循环进行单词翻转.
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两值需要交互,可写为:

  1. A = A^B;
  2. 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亿个数中.

缺点:

  1. 可读性差
  2. 存储的元素个数 <= 元素的最大值,受限于存储空间大小.(比如要存储值为65535的数,就必须要65535/8 = 8k字节的内存,这就导致了位图法不合适,存unsigned int类型的数大约需要232/8 = 5亿字节的内存)(Java没有unsigned int)
  3. 位图对有符号类型数据的存储需要2位来表示一个有符号元素
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值