~~~~~~~持续更新~~~~~~
目录
一、位运算
(1)、二进制中1 的个数
请实现一个函数,输入一个整数,输出该数二进制表示中1 的个数
例:9的二进制表示为1001,有两个1
“1”左移做“&”运算
方法一:
import java.util.Scanner;
public class 二进制中的1数 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int N=sc.nextInt();
System.out.println(Integer.toString(N,2));
int count=0;
//比对每一位
for(int i=0;i<32;i++) {
if((N &(1<<i))==(1<<i)) {
count++;
}
}
System.out.println(count);
方法二:
int count =0;
while(N!=0) {
N=((N-1)& N);
count++;
}
System.out.println(count);
}
(2)、是不是二的整数次方
用一条语句判断一个整数是不是2 的整数次方
他的二进制数中只有一个1
Scanner sc=new Scanner(System.in);
int s=sc.nextInt();
Integer.toString(s,2);
if(((s-1)&s)==0) {
System.out.println("是");
}else {
System.out.println("不是");
}
}
}
( 3)、将整数的奇偶数位互换
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int s=sc.nextInt();
Integer.toString(s,2);
int ou=s & (0xaaaaaaaa);
int ji=s & (0x55555555);
s=(ou>>1)^(ji<<1);
System.out.println(s);
}
}
(4)、0~1之间的浮点实数的二进制表示
给定一个介于0~1 之间的实数,(0.625),类型是double类型的打印他的二进制表示0.101
如果该数字无法精确的用32位以内的二进制表示,则打印“ERROR”
import java.util.Scanner;
public class 二进制小数 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个0到1的浮点数");
double num=sc.nextDouble();
StringBuilder sb=new StringBuilder("0.");
while(num>0) {
double r=num*2;
if(r>=1) {
sb.append("1");
//消除掉整数部分
num=r-1;
}else {
sb.append("0");
num=r;
}
if(sb.length()>34) {
System.out.println("ERRPR");
return;
}
}
System.out.println(sb.toString());
}
}
(5)、出现K次与出现1次
数组中只有一个数出现了一次,其他的数出现了K次,请输出只出现一次的数
2个相同的2进制数做不进位加法,结果为0;
10个相同的10进制数做不进位加法,结果为0;
k个相同的k进制数做不进位加法,结果为0;
package Demo;
public class 出现k次与出现1次 {
public static void main(String[] args) {
int[] arr={2,2,2,9,7,7,7,3,3,3,6,6,6,0,0,0};
int len = arr.length;
char[][] KRadix = new char[len][]; //字符二维数组存取每一个数的三进制的每一位
int k =3;
//记录转成k进制数的最长位数,用来看最后需要计算多少列
int maxLen = 0;
//转成k进制数字
//对于每个数字
for(int i = 0;i<len;i++)
{
//求每个数字的三进制字符串并反转,然后转为字符数组 目的的将每个k进制数的低位对齐(因为转成k进制数的位数不同)
KRadix[i] = new StringBuilder(Integer.toString(arr[i], k)).reverse().toString().toCharArray();
if(KRadix[i].length > maxLen)
{
maxLen = KRadix[i].length;
}
}
//存放做完不进位加法每一位和
int[] resArr=new int[maxLen];
//做不进位加法
for(int i = 0;i<len;i++)
{
//不进位加法
for(int j = 0;j<maxLen;j++)
{
//如果j大于等于当前数组长度的话,就需要补0
if(j >=KRadix[i].length)
{
resArr[j]+=0;
}else
{
resArr[j] += (KRadix[i][j]-'0');
}
}
}
int res = 0;
for(int i = 0;i<maxLen;i++)
{
res+=(resArr[i]%k)*(int)(Math.pow(k, i));
}
System.out.println(res);
}
}
二、 查找与排序
1、递归、查找与排序补充
找重复:n*(n-1)的阶乘,求n-1d阶乘是一个原问题的重复(规模更小)——子问题
找变化:变化的量应该作为参数
找边界:找出口
(1) 求一个数的阶乘
求n的阶乘
public class 求n的阶乘 {
public static void main(String[] args) {
System.out.println(f1(10));
}
static int f1(int n) {
if(n==1) {
return 1;
}
return n*f1(n-1);
}
}
(2) 、数组求和
数组求和
public static void main(String[] args) {
int res= f3(new int [] {1,2,3,4,5},0);
System.out.println(res);
}
static int f3(int[] arr,int begin) {
if(begin==arr.length-1) {
return arr[begin];
}
return arr[begin]+f3(arr,begin+1);
}
}
(3)、字符串的翻转
public static void main(String[] args) {
System.out.println(reversr("asdfgh",5));
}
static String reversr(String str,int end) {
if(end==0) {
return ""+str.charAt(0);
}
return str.charAt(end)+reversr(str, end-1);
}
(4)、斐波那契数列
public class 斐波那契数列 {
public static void main(String[] args) {
System.out.println(fib(6));
}
static int fib(int n) {
if(n==1||n==2) {
return 1;
}
return fib(n-1)+fib(n-2);
}
}
(4)、最大公约数
public class 最大公约数 {
public static void main(String[] args) {
System.out.println(god(18,9));
}
static int god(int m,int n) {
if(n==0) {
return m;
}
return god(n,m%n);
}
}
(5)、 递归改排序
static void insertSort(int [] arr,int k) {
if(k==0) {
return ;
}
//对k-1个元素排序
insertSort(arr,k-1);
//把位置k的元素插入到排序部分
int x=arr[k];
int index=k-1;
while(index>=1&&x<arr[index]) {
arr[index+1]=arr[index];
index--;
}
arr[index]=x;
}
(6)、汉诺塔游戏
public class 汉诺塔游戏 {
public static void main(String[] args) {
// TODO Auto-generated method stub
printHannuoTower(3,"a","b","c");
}
/*
* 将N个盘子从source移动到target的路径打印
* N 初始的N个盘子从小到大,N是最大编号
* form 原始柱子
* to 辅助的柱子
* help 目标柱子
*/
static void printHannuoTower(int N,String form,String to,String help) {
if(N==1) {
System.out.println("将编号为"+N+"从"+form+"移动到"+to);
return;
}
printHannuoTower(N-1,form,help,to);//先把前N-1个盘子挪到辅助空间上
System.out.println("将编号为"+N+"从"+form+"移动到"+to);//N可以顺利到达target
printHannuoTower(N-1,help,to,form);//让N-1从辅助空间回到源空间上
}
}
斐波那契数列问题
等价于两个子问题:
求前一项求前二项两项求和
汉诺塔
1-N从A移动到B,C作为辅助
等价于;
1~N-1从A移动到CB为辅助
把N从A移动到B
1--N-1从C移动到B为辅助
全范围内二分查找
等价于三个子问题:
左边找(递归)中间比
右边找(递归)
注意:左查找和右查找只选其一
2、如何评估算法性能
如何评估一个算法性能?什么是大O表示法?
数组有序,顺序查找和二分查找哪个更快,快多少?
- n!的弱上界是n^n,因此增长速度非常快,这意味着单位时间内可求解的问题很小,换言之,超慢
- 2^n这样的指数函数增长非常快,这种算法可以认为超慢
- 0(n2)和O(n3)增长很快,算法很慢,至少优化到nlgn,O(n2)的有冒泡排序,直接插入排序,选择排序
- nlgn可以认为是及格的算法吧,一般分治法可以缩小层数为1gn,而每层的复杂度一般为O(n),例如归并排序算法、快速排序算法
- 0(n)叫做线性算法,这种算法比较优秀,或者问题本身比较简单,比如求连续求和最大子数组的线性解
- 0(sqrt(n))当然比O(n)更快,不是没有,但这种很少
- 1gn就是很优秀的算法了,比如二分查找法,但是这种算法往往对输入数据的格式是有要求的,二分查找要求输入数据有序
- 还有一种是常量,无论规模怎么扩大,都花固定时间,这是为数极少的效率最高的算法了,多数是数据很规则
(1)、 顺序查找、二分查找分析:n与lgn
直观感受性能差别
public class 顺序查找与二分查找 {
public static void main(String[] args) {
int [] x=new int[10000*10000];
for(int i=0;i<x.length;i++) {
x[i]=i+1;
}
int target=10000*10000;
long now =System.currentTimeMillis();
int index=brinarySearch(x,0,x.length-1,target);
System.out.println(System.currentTimeMillis()-now+"ms");
System.out.println(target+"所在位置为:"+index);
//调用顺序查找
now =System.currentTimeMillis();
index=search(x,target);
System.out.println(System.currentTimeMillis()-now+"ms");
}
//二分查找
private static int brinarySearch(int arr[],int low,int high,int key) {
while(low<=high) {
int mid=low+((high-low)>>1);//(high-low)>>1,防止溢出,移位也更高,同时,每次循环都要更新
int midVal=arr[mid];
if(midVal<key) {
low=mid+1;
}else if(midVal>key) {
high=mid-1;
}else {
return mid;//key found
}
}
return -(low+1);//key not found
}
//顺序查找
private static int search(int arr[],int key) {
for(int i=0;i<arr.length;i++) {
if(arr[i]==key) {
return i;
}
}
return -1;
}
}
0ms
100000000所在位置为:99999999
58ms
(2)、冒泡、插入、选择排序的分析:n^与nlgn
直观感受性能差别:和Arrays.sort()的执行时间对比
冒泡:
插入:
选择:
import java.util.Arrays;
public class 冒泡插入排序 {
public static void main(String[] args) {
//比较冒泡排序,选择排序,快速排序的效率
int[] arr = generateRandomArray(100000, 100000);
int[] a = copyArray(arr); //冒泡排序
int[] b = copyArray(arr); //选择排序
int[] c = copyArray(arr); //快速排序
int[] d = copyArray(arr); //插入排序
long start = System.currentTimeMillis();
BubbleSort(a);
long end = System.currentTimeMillis();
System.out.println("冒泡排序所用时间:"+(end-start)+"ms");
long start1 = System.currentTimeMillis();
SelectionSort(b);
long end1 = System.currentTimeMillis();
System.out.println("选择排序用时:"+(end1-start1)+"ms");
long start2 = System.currentTimeMillis();
InsertionSort(d);
long end2 = System.currentTimeMillis();
System.out.println("插入排序用时:"+(end2-start2)+"ms");
long start3 = System.currentTimeMillis();
QuickSort(c);
long end3 = System.currentTimeMillis();
System.out.println("Arrays.sort()用时:"+(end3-start3)+"ms");
}
/**冒泡排序*/
public static void BubbleSort(int[] arr) {
int temp;
for(int i = 0;i<arr.length-1;i++) {
for(int j = 0;j<arr.length-1-i;j++) {
if(arr[j]>arr[j+1]) {
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
/**选择排序*/
public static void SelectionSort(int[] arr) {
int temp;
for(int i = 0;i<arr.length;i++) {
int min = arr[i];
int minIndex = i;
for(int j = i+1;j<arr.length;j++) {
if(arr[j]<min) {
min = arr[j];
minIndex = j;
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
/**插入排序*/
public static void InsertionSort(int[] arr){
int j;
for (int i=1;i<arr.length;i++){
int t=arr[i];
for (j=i;j>0&&t<arr[j-1];j--){
arr[j]=arr[j-1];
}
arr[j]=t;
}
}
/**快递排序*/
public static void QuickSort(int[] arr) {
Arrays.sort(arr);
}
/**
* 生成随机数组
* @param maxSize 最大长度
* @param maxValue 最大值
* @return
*/
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
/**复制数组*/
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
}
冒泡排序所用时间:19141ms
选择排序用时:3029ms
插入排序用时:3173ms
Arrays.sort()用时:101ms
(3)、典型递归算法的性能分析
例题
1、旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转,输入一个递增排序的数组的一个旋转,输出的旋转数组的最小元素,例如数组{3、4、5、1、2}为{1、2、3、4、5}的一个旋转,该数组的最小值为1.
public class 旋转数组的最小数字 {
public static void main(String[] args) {
int [] arr= {3,4,5,1,2};
int res=min(arr);
System.out.println(res);
// arr=new int[] {2,3,4,5,6};
// res=min(arr);
// arr=new int[] {1,0,1,1,1};
// res=min(arr);
// System.out.println(res);
}
static int min(int[] arr) {
int begin =0;
int end=arr.length-1;
//考虑没有旋转这种特殊的旋转
if(arr[begin]<arr[end]) return arr[begin];
//begin 和end指向相邻的元素,退出
while(begin+1<end) {
int mid=begin+((end-begin)>>1);
//要么左侧有序要么右侧有序
if(arr[mid]>=arr[begin]) {//左侧有序
begin=mid;
}else {
end=mid;
}
}
return arr[end];
}
}