40. 数组中只出现一次的数字
题:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
思路:1)HashMap
2)异或:两个相同数字异或=0,一个数和0异或还是它本身。
依次异或,剩下的肯定是那两个只出现一次的数的异或结果。
这个结果的二进制中的1,表现的是A和B的不同的位。 我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。
注意 :在异或的时候,分组方法很重要。即:为1 的位代表异或的两个数在该位取了不同的值,所以以该位为标准,将该位为1的存入num1[0],反之存入num2[0],然后每次就在两个数组内部异或,最后留下的就是所求。
代码:
1) hashMap:
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int count = 0;
int length = array.length;
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0;i < length; i++) {
if (!map.containsKey(array[i])) {
map.put(array[i],1);
}
else {
map.remove(array[i]);
map.put(array[i],2);
}
}
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
if (1 == (int)entry.getValue()) {
if (count == 0) {
num1[0] = (int) entry.getKey();
count++;
}
else {
num2[0] = (int) entry.getKey();
break;
}
}
}
}
2)※异或
public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {
int length = array.length;
if(length == 2){
num1[0] = array[0];
num2[0] = array[1];
return;
}
int bitResult = 0;
for(int i = 0; i < length; ++i){
bitResult ^= array[i];
}
int index = findFirst1(bitResult);
for(int i = 0; i < length; ++i){
if(isBit1(array[i], index)){
num1[0] ^= array[i];
}else{
num2[0] ^= array[i];
}
}
}
private int findFirst1(int bitResult){
int index = 0;
while(((bitResult & 1) == 0) && index < 32){
bitResult >>= 1;
index++;
}
return index;
}
private boolean isBit1(int target, int index){
return ((target >> index) & 1) == 1;
}
42. 和为S的正整数序列
题:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。
思路:
1) 重点是回溯
2) 滑动时间窗口
代码:
1) 回溯
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> resList = new ArrayList<>();
int nowSum = 0;
int start = 1;
for (int i = 1 ;i <= sum ; i++) {
nowSum += i;
if (nowSum > sum) {
i = start ;
start++;
nowSum = 0;
}
else if (nowSum == sum) {
if (start == i) {
break;
}
ArrayList<Integer> list = new ArrayList<>();
for (int j = start; j <= i; j++) {
list.add(j);
}
resList.add(list);
start = start + 1;
i = start;
nowSum = start;
}
else {
continue;
}
}
return resList;
}
2) 滑动时间窗口
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
//存放结果
ArrayList<ArrayList<Integer> > result = new ArrayList<>();
//两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
int plow = 1,phigh = 2;
while (phigh > plow) {
// 等差数列求和Sn = n(a1+an)/2
int nowSum = (plow+phigh) * (phigh-plow+1) / 2;
if (nowSum == sum) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = plow; i <= phigh; i++) {
list.add(i);
}
result.add(list);
plow++;
}
else if (nowSum < sum) {
phigh++;
}
else {
plow++;
}
}
return result;
}
42. 和为S的两个数字
题:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
思路:
1) 双指针,类比快排
一个指针从前往后,一个指针从后往前。大的话后面的往前移,小的话前面的往后移。相等的时候要比较乘积。不能跳出循环,一定要遍历完所有的数字
重点是 比较完相等记得探针要改变,否则无限循环
Arraylist是怎么存怎么取的,所以因为要求小的先输出,本来就是从小到大排序,所以就按照顺序存入即可。
2) HashMap
map.put(array[i], 1),map.put(sum - array[i],1)
重点是:当有多个相同的数字的时候的情况,要考虑完全!
代码: 1) 双指针
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
ArrayList<Integer> list = new ArrayList<>();
int i = 0, j = array.length - 1;
while (i < j) {
while (array[i] + array[j] > sum && i < j) {
j--;
}
while (array[i] + array[j] < sum && i < j) {
i++;
}
if (array[i] + array[j] == sum) {
if (list.isEmpty()) {
list.add(array[i]);
list.add(array[j]);
} else {
int multi1 = list.get(0) * list.get(1);
int multi2 = array[i] * array[j];
if (multi2 < multi1) {
list.clear();
list.add(array[i]);
list.add(array[j]);
}
}
i++;
j--;
}
}
return list;
}
2) HashMap:
public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
ArrayList<Integer> list = new ArrayList<>();
if (array == null || array.length <= 0) {
return list;
}
Map<Integer,Integer> map = new HashMap<>();
int count = 0;
for (int i = 0; i < array.length; i++) {
if (map.containsKey(array[i])) {
if (count == 0) {
list.add(sum-array[i]);
list.add(array[i]);
count++;
}
int mul = array[i] * (sum - array[i]);
if (mul < (list.get(0)) * list.get(1)) {
list.clear();
list.add(sum-array[i]);
list.add(array[i]);
}
}else {
map.put(array[i],1);
if (sum - array[i] != array[i]) {
map.put(sum-array[i],1);
}
else {
int tmp = map.get(array[i]) + 1;
map.remove(array[i]);
map.put(array[i],tmp);
}
}
}
// Collections.sort(list);
return list;
}
43. 左旋转字符串
题:对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。
思路:
1)StringBuilder拼接
2)子串
3)三次旋转(YX = (XTYT)T)
注:swap的时候,把String转成字符串数组,是为了更改的时候比较好更改。
代码:
1)StringBuilder:
public String LeftRotateString(String str,int n) {
if (n > str.length()) {
return "";
}
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < n; i++) {
sb1.append(str.charAt(i));
}
for (int i = n; i < str.length(); i ++) {
sb2.append(str.charAt(i));
}
StringBuilder newStr = sb2.append(sb1);
return newStr.toString();
}
2)子串
public String LeftRotateString(String str,int n) {
int length = str.length();
if (length <= 0) return "";
n = n % length;
str += str;
return str.substring(n,n+length);
}
3)旋转
public String LeftRotateString(String str,int n) {
int len = str.length();
if (len <= 0) return "";
n = n % len;
char[] array = str.toCharArray();
for (int i = 0,j = n-1;j > i; i++,j--) {
swap(array,i,j);
}
for (int i = n,j =len-1;j > i; i++,j--) {
swap(array,i,j);
}
for (int i = 0,j = len-1;j > i; i++,j--) {
swap(array,i,j);
}
return new String(array);
}
44. 翻转单词序列
题:例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
思路:先翻转整个句子,再翻转每个单词
代码:
public String ReverseSentence(String str) {
if (str == null || str.length() <= 0) {
return "";
}
int len = str.length();
char[] chars = str.toCharArray();
/**先反转整个句子*/
for (int i = 0,j = len - 1; j > i; i++, j--) {
swap(chars,i,j);
}
/**再反转每个单词*/
int i = 0;
int s = 0, e = 0;
while (i < len) {
// 找到第一个字符开始的位置
while (i < len && chars[i] == ' '){
i++;
}
s = e = i;
// 找到这个单词最后一个字符的位置,跳出的时候e是空格
while (i < len && chars[i] != ' ') {
i++;
e++;
}
reverseWords(chars,s,e-1);
}
return new String(chars);
}
private void swap(char[] chars,int i,int j) {
char tmp = chars[i];
chars[i] = chars[j];
chars[j] = tmp;
}
private void reverseWords(char[] chars,int start,int end) {
for (int i = start,j = end; j > i; i++,j--) {
swap(chars,i,j);
}
}
45. 扑克牌顺子
题:大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
思路:
1) 不能有重复(除了0)
2) 差的绝对值要 < 5
代码:
public boolean isContinuous(int [] numbers) {
if (numbers == null || numbers.length <= 0) return false;
Map<Integer,Integer> map = new HashMap<>();
int min = 99;
int max = -1;
int count0 = 0;
for (int i = 0 ; i < numbers.length; i++) {
if (!map.containsKey(numbers[i])) {
map.put(numbers[i],1);
if (numbers[i] < min && numbers[i] != 0) {
min = numbers[i];
}
if (numbers[i] > max && numbers[i] != 0) {
max = numbers[i];
}
if (max - min >= 5) {
return false;
}
}
else {
// 除了0不能重复
if (numbers[i] != 0) {
return false;
}
else {
continue;
}
}
}
return true;
}
※46. 孩子们的游戏
题:随机指定一个数m,让编号为0的开始报数。每次喊到m-1的那个不再回到圈中,从他的下一个开始,继续0…m-1报数…直到剩下最后一个小朋友,可以不用表演,请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)。如果没有小朋友,请返回-1
思路:直接用模拟游戏过程
注:使用LinkedList优于ArrayList,因为只涉及插入和删除,不涉及查询,链表效率更高
代码:
public int LastRemaining_Solution(int n, int m) {
if(n == 0 || m == 0) return -1;
List<Integer> list = new LinkedList<>();
for (int i = 0;i < n; i++) {
list.add(i);
}
int bt = 0;
while (list.size() > 1) {
bt = (bt + m - 1) % list.size();
list.remove(bt);
}
return list.size() == 1 ? list.get(0) : -1;
}
47. 1+2+3+…+n
题:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路:短路求值!!,利用逻辑运算的性质,比如&&运算当前一部分为false的时候后面就不会执行,模拟递归结束。
代码:
public int Sum_Solution(int n) {
int sum = n;
boolean ans = (sum > 0) && (sum += Sum_Solution(n-1))>0;
return sum;
}
48. 不用加减乘除做加法
题:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
思路:用二进制与运算代替
加法:异或操作^;进位:与操作&再左移一位。
按照十进制的思路来,例如5+7
二进制就是101+111,先算加法(不带进位)即101^111=010;再算进位(纯进位)(101&111)<<1 = 1010;
再重复上面的(此时num1=加法结果010,num2=进位结果1010)直到进位为0
代码:
public int Add(int num1,int num2) {
while (num2 != 0) {
int sum1 = num1 ^ num2;
num2 = (num1 & num2) << 1;
num1 = sum1;
}
return num1;
}
49. 把字符串转换成整数
题:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。输入一个字符串,包括数字字母符号,可以为空。如果是合法的数值表达则返回该数字,否则返回0
思路:
有几个注意点:
1)前面有没有符号位
2)溢出的处理(尤其是 负数是-2147483648,正数是+2147483647),代码里面的重点
代码:
public int StrToInt(String str) {
if (str == null || str.length() <= 0) {
return 0;
}
char[] array = str.toCharArray();
int flag = 1;
if (array[0] == '+') {
flag = 1;
}
if (array[0] == '-') {
flag = -1;
}
int res = 0;
for (int i = ((array[0] == '+' || array[0] == '-') ? 1 : 0); i < array.length; i++) {
if (!(array[i] >= '0' && array[i] <= '9')) {
return 0;
}
// 溢出处理
if(flag == 1 && res * 10 > Integer.MAX_VALUE - (int)(array[i] - '0'))
return 0;
if(flag == -1 && res * (-10) < Integer.MIN_VALUE + (int)(array[i] - '0'))
return 0;
res = res * 10 + (array[i] - '0');
}
return res * flag;
}