剑指offer 11-15
11 二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示
分析
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
(搬运评论区大佬的解释)
如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
public class numberOfBinary_11 {
public static void main(String[] args) {
numberOfBinary_11 sum = new numberOfBinary_11();
int res = sum.NumberOf1(10);
System.out.println(res);
}
public int NumberOf1(int n) {
if(n == 0) {
return 0;
}
int count = 0;
while(n != 0) {
count++;
n = n & (n-1);
}
return count;
}
}
12 数值的整数次方
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0
//1 直接运算 时间复杂度:O(n),其中n为所求的次方数,一共需要乘n次 public double Power(double base, int exponent) { if(exponent == 0) return 1; double res = 1; for(int i = 1; i <= Math.abs(exponent); i++){ res = res * base; } if(exponent < 0) return 1 / res; return res; } //2 方法二:快速幂(扩展思路) // 知识点:分治: /*计算幂运算,我们还可以使用快速幂快速计算。 如果我们要计算5105^{10}510,常规的算法是5∗5=255*5=255∗5=25,
然后再25∗5=12525*5=12525∗5=125,如此往下,一共是999次运算,即n−1n-1n−1次。但是我们可以考虑这样:5∗5=255*5=255∗5=25(二次)、
25∗25=62525*25=62525∗25=625(四次)、625∗625=...625*625=...625∗625=...(八次),这是一个二分的思维,运算次数缩减到了log2nlog_2nlog2n次*/ public class Solution { //快速幂 private double Pow(double x, int y){ double res = 1; while(y != 0){ //可以再往上乘一个 if((y & 1) != 0) res *= x; //叠加 x *= x; //减少乘次数 y = y >> 1; } return res; } public double Power(double base, int exponent) { //处理负数次方 if(exponent < 0){ base = 1 / base; exponent = -exponent; } return Pow(base, exponent); } }
13调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
public class adjustArrays_13 {
public static void main(String[] args) {
adjustArrays_13 adjust = new adjustArrays_13();
int[] array = {1,2,3,4,6,8,9,11};
int[] arr = adjust.reOrderArray(array);
for(int i : array) {
System.out.println(i);
}
}
public int[] reOrderArray(int[] array) {
int[] reArray1 = new int[array.length];
int[] reArray2 = new int[array.length];
int m=0;
int n=0;
for(int i=0;i<array.length;i++){
if(array[i]%2!=0){
reArray1[m++]=array[i];
}
if(array[i]%2==0){
reArray2[n++]=array[i];
}
}
for(int i=0;i<m;i++){
array[i]=reArray1[i];
}
for(int i=0;i<n;i++){
array[i+m]=reArray2[i];
}
return array;
}
}
还有另外一种方法
i++
往前走碰到偶数停下来,j = i+1
- 若
a[j]
为偶数,j++
前进,直到碰到奇数如果j==len-1
时还没碰到奇数,证明i
和j
之间都为偶数了,完成整个移动 a[j]
对应的奇数插到a[i]位置,j
经过的j-i
个偶数依次后移
public int[] reOrderArray2(int[] array) {
int len = array.length;
if(len <= 1){ // 数组空或长度为1
return null;
}
int i = 0;
while(i < len){
int j = i + 1;
if(array[i]%2 == 0){ // a[i]为偶数,j前进,直到替换
while(array[j]%2 == 0){ // j为偶数,前进
if(j==len-1)// i为偶数,j也为偶数,一直后移到了末尾,证明后面都是偶数
return array;
j++;
}
// 此时j为奇数
int count = j-i;
int temp = array[i];
array[i] = array[j];
while(count>1){
array[i+count] = array[i+count-1];//数组后移
count--;
}
array[i+1] = temp;
}
i++;
}
return array;
}
14 链表中倒数第K个结点
输入一个链表,输出该链表中倒数第k个结点
分析:
快指针先往前走k步,注意判断边界,然后快慢一起走,当快指针为none的时候,慢指针走到了倒数第k个节点
package list;
import java.util.Scanner;
public class findKthToTail_14 {
public static void main(String[] args) {
ListNode2 l = new ListNode2(-1);
ListNode2 l1 = l;
Scanner s = new Scanner(System.in);
while (!s.hasNext("!")) {
l1.next = new ListNode2(s.nextInt());
l1 = l1.next;
}
ListNode2 node = FindKthToTail(l.next, 2);
System.out.println(node.val);
}
//快指针先往前走k步,注意判断边界,然后快慢一起走,当快指针为none的时候,慢指针走到了倒数第k个节点
public static ListNode2 FindKthToTail(ListNode2 head,int k) {
if(k <= 0 || head == null) {
return null;
}
ListNode2 fast = head;
ListNode2 slow = head;
for(int i = 0; i < k; i++) {
if(fast == null) {
return null;
}
fast = fast.next;
}
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
class ListNode2{
int val;
ListNode2 next;
public ListNode2(int val) {
this.val = val;
}
}
15 反转链表
输入一个链表,反转链表后,输出新链表的表头。
分析
- 题目所给的是单链表,想了一下反转后的样子:最后一个结点指向倒数第二个,倒数第二个指向倒数第三个,......,第二个指向第一个,第一个指向null;
- 知道了反转后各个结点指向哪之后,就需要开始调整每个结点的next指针。
- 这就需要把结点挨个从链表上摘下来,做调整;
- 这个调整过程需要两个指针辅助:pre记录其前一个结点位置,好让该结点的next指针指向前一个结点,但是在指向前一个结点前需要用一个指针p记录后一个结点地址,避免结点丢失。
- 例子:
- 以head结点为例步骤如下:
- 1.反转后head是指向null,所以未反转的时候其前一个结点应该是null,初始化pre指针为null;
- 2.用p指针记录head的下一个结点head.next;
- 3.从链表上摘下head,即让head.next指向pre;
- 4.此时已完成head结点的摘取及与前一个节点的连接,则我们需要操作下一个结点:故需移动pre和head,让pre指向head,head指向下一个节点。
- 重复这四个操作直到head走完原链表,指向null时,循环结束,返回pre.
注:上文的p在代码中为next;
package list;
import java.util.Scanner;
public class reverseList_15 {
public static void main(String[] args) {
ListNode3 l = new ListNode3(-1);
ListNode3 l1 = l;
Scanner s = new Scanner(System.in);
while (!s.hasNext("!")) {
l1.next = new ListNode3(s.nextInt());
l1 = l1.next;
}
ListNode3 node = ReverseList(l.next);
while (node != null) {
System.out.print(node.val + " ");
node = node.next;
}
}
public static ListNode3 ReverseList(ListNode3 head) {
if (head == null || head.next == null) {
return head;
}
ListNode3 pre = null;
ListNode3 next = null;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
class ListNode3 {
int val;
ListNode3 next;
public ListNode3(int val) {
this.val = val;
}
}