一、链表
1.1基本操作
//单链表的数据结构
class Node {
Node next=null;
int data;
public Node(int data) {
this.data=data;
}
}
//链表基本操作
class MyLinkedList{
Node head=null;
//插入数据
public void addNode(int d) {
Node newNode=new Node(d);
if(head==null) {
head=newNode;
return ;
}
Node tmp=head;
//不能写tmp!=null
while(tmp.next!=null) {
tmp=tmp.next;
}
tmp.next=newNode;
}
//删除元素
public boolean deleteNode(int index) {
if(index<1 || index>length()) return false;
if(index==1) {
head=head.next;
return true;
}
int i=2;
Node preNode=head;
Node curNode=preNode.next;
while(i<index) {
preNode=curNode;
curNode=curNode.next;
i++;
}
preNode.next=curNode.next;
return true;
}
//返回链表长度
public int length() {
int i=0;
Node tmp=head;
while(tmp!=null) {
tmp=tmp.next;
i++;
}
return i;
}
//排序
public Node orderList() {
Node nextNode=null;
int tmp=0;
Node curNode=head;
//双层遍历;交换;选小的
while(curNode.next!=null) {
nextNode=curNode.next;
while(nextNode!=null) {
if(curNode.data>nextNode.data) {
tmp=curNode.data;
curNode.data=nextNode.data;
nextNode.data=tmp;
}
nextNode=nextNode.next;
}
curNode=curNode.next;
}
return head;
}
public void printNode() {
Node tmp=head;
while(tmp!=null) {
System.out.print(tmp.data+" ");
tmp=tmp.next;
}
}
}
public class MyLinkedListExamples {
public static void main(String[] args){
MyLinkedList list=new MyLinkedList();
list.addNode(5);
list.addNode(2);
list.addNode(4);
System.out.println("listLength:"+list.length());
System.out.println("before order:");
list.printNode();
System.out.println();
System.out.println("after order:");
list.printNode();
}
}
listLength:3
before order:
5 2 4
after order:
5 2 4
1.2 删除重复数据
//删除重复数据
public void deteDuplecate(Node head) {
//建立一个Hashtable
Hashtable<Integer,Integer> table=new Hashtable<Integer,Integer>();
Node tmp=head;
Node pre=null;
while(tmp!=null) {
//若表中已有则删除
if(table.containsKey(tmp.data))
pre.next=tmp.next;
else {
table.put(tmp.data,1);
pre=tmp;
}
tmp=tmp.next;
}
}
public void deteDuplecate1(Node head) {
Node p=head;
//双循环
while(p!=null) {
Node q=p;
while(q.next!=null) {
if (p.data==q.next.data)
q.next=q.next.next;
else
q=q.next;
}
p=p.next;
}
}
1.3找出链表倒数第k个元素
//快慢指针;快指针先走k步;
public Node findElem(Node head,int k) {
if(k<=0)return null;
Node p1=head;
Node p2=head;
while(k>0&&p1!=null) p1=p1.next;
if(p1==null) return null;
while(p1!=null) {
p1=p1.next;
p2=p2.next;
}
return p2;
}
1.4判断是否有环
//检测是否有环
//快慢指针,快指针进两步,慢指针进一步
public boolean isLoop(Node head) {
Node fast=head;
Node slow=head;
if(fast==null)return false;
while(fast!=null&&fast.next!=null) {
slow=slow.next;
fast=fast.next.next;
//若有环则必存在相等的情况
if(fast==slow) {
return true;
}
}
return !(fast==null||fast.next==null);
}
1.5判断是否相交
//判断尾结点是否相同
public boolean isIntersect(Node h1,Node h2) {
if(h1==null||h2==null)return false;
Node tail1=h1;
while(tail1!=null)tail1=tail1.next;
Node tail2=h2;
while(tail2!=null)tail2=tail2.next;
return tail1==tail2;
}
二、栈与队列
栈:后进先出;
队列:先进先出
2.1栈
2.1.1栈实现
//数组实现
package dataStructure;
import java.util.Arrays;
public class Mystack <E>{
private Object[] stack;
private int size;//存储元素的个数
public Mystack() {
stack=new Object[10];
}
//判断栈是否为空
public boolean isEmpty() {
return size==0;
}
public E peek() {
if(isEmpty())return null;
return (E)stack[size-1];
}
//弹出
public E pop() {
E e=peek();
if(e!=null)
{
stack[size-1]=null;
size--;}
return e;
}
//弹入
public E push(E item) {
ensureCapacity(size+1);
stack[size++]=item;
return item;
}
//判断数组是否已经满;若满则扩容
public void ensureCapacity(int size) {
int len=stack.length;
//扩容,每次长度+10
if(len<size)stack=Arrays.copyOf(stack,this.size+10);
}
public static void main(String[] args) {
Mystack<Integer> s=new Mystack<Integer>();
for(int i=0;i<12;i++) {
s.push(i);
}
System.out.print(s.size);
}
}
//链表实现
package dataStructure;
import java.util.Arrays;
//链表数据结构
class Node<E>{
Node<E> next=null;
E data;
public Node(E data) {
this.data=data;
}
}
public class Mystack <E>{
Node<E> top=null;
public boolean isEmpty() {
return top==null;
}
public void push(E data) {
Node<E> newNode=new Node<E>(data);
//注意指向;为了满足栈特性
newNode.next=top;
top=newNode;
}
public E pop() {
if(isEmpty())return null;
else {
E data=top.data;
top=top.next;
return data;
}
}
public E peek() {
if(isEmpty())return null;
return top.data;
}
}
2.1.2
2.2队列
//链表形式
package dataStructure;
class Node<E> {
Node<E>next=null;
E data;
public Node(E data) {
this.data=data;
}
}
public class MyQueue<E>{
private Node<E> head=null;
private Node<E> tail=null;
public boolean isEmpty() {
return tail==head;
}
public void put(E data) {
Node<E> newNode=new Node<E>(data);
if(head==null&&tail==null)
head=tail=newNode;
else {
tail.next=newNode;
tail=newNode;
}
}
public E pop() {
if(isEmpty()) return null;
E data=head.data;
head=head.next;
return data;
}
public int size() {
Node<E> tmp=head;
int n=0;
while(tmp!=null) {
n++;
tmp=tmp.next;
}
return n;
}
}
//线程安全的数组形式
public class MyQueue<E>{
private LinkedList<E> list=new LinkedList<E>();
private int size=0;
public synchronized void put(E e) {
list.add(e);
size++;
}
public synchronized E pop() {
size--;
return list.removeFirst();
}
public synchronized boolean empty() {
return size==0;
}
public synchronized int size() {
return size;
}
}
2.3用两个栈模拟队列
public class MyQueue<E>{
//s1负责入栈
private Stack<E> s1=new Stack<E>();
//s2负责出栈
private Stack<E> s2=new Stack<E>();
public synchronized E pop() {
if(s2.isEmpty()) {
while(!s1.isEmpty())
s2.push(s1.pop());
}
return s2.pop();
}
public synchronized void put(E e) {
s1.push(e);
}
public synchronized boolean empty() {
return s1.isEmpty()&&s2.isEmpty();
}
}
3、排序
//选择排序:对于一组记录,经过第一轮比较之后得到最小的记录,然后让这个记录与第一个记录交换位置,接着对不包括第一个元素的其他记录进行第二轮比较、交换,重复该操作,直到就剩一个元素为止;
public static void selectSort(int[] a) {
for(int i=0;i<a.length;i++) {
int index=i;
for(int j=i+1;j<a.length;j++) {
if (a[index]>a[j])
index=j;
}
int tmp=a[index];
a[index]=a[i];
a[i]=tmp;
}
}
//插入排序有点难:初始第一个记录自成一个有序序列,接着从第二个开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直到最后一个记录也插入到有序序列中为止;
public static void insertSort(int[] a) {
for(int i=1;i<a.length;i++) {
int tmp=a[i],j=i;
while(j>0&&tmp<a[j-1]) {
a[j]=a[j-1];
j--;
}
a[j]=tmp;
}
}
//冒泡排序,从第一个元素开始两两比较,若前者大于后者则交换,一轮过后最大的记录将放在最后,重复该过程直到只剩一个元素为止
public static void bubbleSort(int[] a) {
for(int i=a.length-1;i>=0;i--) {
for(int j=0;j<i;j++) {
if(a[j]>a[j+1]) {
int tmp=a[j+1];
a[j+1]=a[j];
a[j]=tmp;
}
}
}
}
//归并:递归+分治;对于一组记录(假设n个)
//首先将每两个相邻的长度为1的子序列归并,得到n/2(向上取整)个长度为2或1的有序子序列,
//再将其两两归并,反复执行此过程,直到得到一个有序序列;
public static void mergeSort(int array[], int p,int r) {
if(p<r) {
int q=(p+r)/2;
mergeSort(array,p,q);
mergeSort(array,q+1,r);
merge(array,p,q,r);
}
}
public static void merge(int array[],int p,int q,int r) {
int i,j,k,n1,n2;
n1=q-p+1;
n2=r-q;
//建立需要归并的两个数组
int[] L=new int[n1];
int[] R=new int[n2];
//
for( i=0,k=p;i<n1;i++,k++) {
L[i]=array[k];
}
for(j=0,k=q+1;j<n2;j++,k++) {
R[j]=array[k];
}
//比较两数组元素的大小,小的放在array中,更新索引
for(k=p,i=0,j=0;i<n1&&j<n2;k++) {
if(L[i]>R[j]) {
array[k]=R[j];
j++;
}else {
array[k]=L[i];
i++;
}
}
//判断谁还有剩余元素
if(i<n1) {
for(;i<n1;i++,k++)
array[k]=L[i];
}
if(j<n2) {
for(;j<n2;j++,k++)
array[k]=R[j];
}
}
//快排:分而治之;对于一组记录,经过一趟排序,将原序列分成两部分,其中前一部分均比后一部分的所有记录小,然后再依次对前后两部分进行快排,递归,直到全部有序
public static void quickSort(int[] a) {
sort(a,0,a.length-1);
}
public static void sort(int[] a,int low,int high) {
if(low>high) return;
int i=low,j=high;
int value=a[i];
while(i<j) {
while(i<j&&a[j]>=value)j--;
if(i<j)
a[i]=a[j];
while(i<j&&a[i]<value)i++;
if(i<j)
a[j]=a[i];
}
a[i]=value;
sort(a,low,i-1);
sort(a,i+1,high);
}
//希尔排序:先将待排序的数组元素分成多个子序列使得每个子序列的元素个数相对较少,然后对每个子序列分别进行直接插入排序,待整个待排序序列基本有序后,最后再对所有元素进行一次直接插入排序
public static void shellSort(int[] array) {
int length=array.length;
int i,j,h;
int temp;
//定义步长
for(h=length/2;h>0;h=h/2) {
//直接插入进阶版
//
for(i=h;i<length;i++) {
temp=array[i];
//注意=
for(j=i-h;j>=0;j-=h) {
if(temp<array[j]) {
array[j+h]=array[j];
}else
break;
}
array[j+h]=temp;
}
}
}
//堆排序:构建堆,交换堆顶元素与最后一个元素的位置;
public static void myMinSheapSort(int[] array) {
int i,len=array.length;
//整理成大根堆
for(i=len/2-1;i>=0;i--)
adjustHeapSort(array,i,len-1);
//每次选出最大值“沉底”;
for(i=len-1;i>=0;i--) {
int tmp=array[0];
array[0]=array[i];
array[i]=tmp;
adjustHeapSort(array,0,i-1);
}
}
public static void adjustHeapSort(int[] a,int pos,int len) {
int tmp;
int child;
//更新
for(tmp=a[pos];2*pos+1<=len;pos=child) {
child=2*pos+1;
//选取孩子结点较大的记录
if((child+1)<=len&&a[child]<a[child+1]) {
child++;
}
//若父结点小于孩子结点就交换,反之退出
if(tmp<a[child])
a[pos]=a[child];
else
break;
}
//放到合适位置
a[pos]=tmp;
}
4、位运算
4.1判断一个数是否是2的n次方
//判断一个数是否是2的n次方:1去移位,看是否相等
public static boolean isPower(int n) {
if(n<1)return false;
int i=1;
while(i<n) {
if(i==n)
return true;
i<<=1;
}
return false;
}
//判断一个数是否是2的n次方等价于它的二进制是否只有一个1;
public static boolean isPower1(int n) {
if(n<1)return false;
//判断条件若为0,则满足条件
int m=n&(n-1);
return m==0;
}
4.2 求二进制中1的个数
public static int countOne(int n) {
//计数器满足条件就加1
int count=0;
while(n>0) {
//判断最后移位是否为1
if((n&1)==1)
count++;
//右移
n>>=1;
}
return count;
}
//进阶版:给定一个数,每次进行一次n&(n-1)计算,其结果都会少一个1;还是最后的那个1
public static int countOne(int n){
int count=0;
while(n>0){
if(n!=0){
n=n&(n-1);
count++;
}
}
return count;
}
5、数组
5.1寻找数组的最大值最小值
//双元素设定最大值max最小值min标记,每次比较相邻的两个数,较大值与max比较,较小值与min比较
static int maxValue;
static int minValue;
public static void getMinAndMax(int[] array) {
maxValue=array[0];
minValue=array[0];
int len=array.length;
for(int i=1;i<len-1;i+=2) {
//比较相邻的两个数
if(array[i]<array[i+1]) {
if(array[i]>maxValue)
maxValue=array[i];
if(array[i+1]<minValue)
minValue=array[i+1];
}else {
if(array[i+1]>maxValue)
maxValue=array[i+1];
if(array[i]<minValue)
minValue=array[i];
}
}
//对最后的元素特别处理
if(array[len-1]<minValue)
minValue=array[len-1];
if(array[len-1]>maxValue)
maxValue=array[len-1];
}
5.2求最大子数组之和
//优化的动态规划只需要O(n);
public static int max(int m, int n) {
return m>n?m:n;
}
//求最大子数组
public static int maxSubArray(int[] arr) {
int n=arr.length;
//最大子数组和
int nAll=arr[0];
int nEnd=arr[0];
for(int i=1;i<n;i++) {
//以i结尾的元素的最大子数组和
nEnd=max(nEnd+arr[i],arr[i]);
//更新最大子数组和
nAll=max(nAll,nEnd);
}
return nAll;
}
//记录位置
private static int begin;
private static int end;
public static int maxSubArr(int[] arr) {
//最大子数组和
int maxSum=Integer.MIN_VALUE;
//以n结尾的最大子数组和
int nSum=0;
int nStart=0;
for(int i=0;i<arr.length;i++) {
//动态规划变式
if(nSum<0) {
nSum=arr[i];
nStart=i;
}
else {
nSum=nSum+arr[i];
}
//更新
if(nSum>maxSum) {
maxSum=nSum;
begin=nStart;
end=i;
}
}
return maxSum;
}
5.3求数组中重复次数最多的元素
//map数据结构用键值记录出现次数
public static int findMostFrequentInArray(int[] a) {
int result=0;
int size=a.length;
if(size==0)return Integer.MAX_VALUE;
//初始化
Map<Integer,Integer> m=new HashMap<Integer,Integer>();
for(int i=0;i<size;i++) {
if(m.containsKey(a[i])) {
m.put(a[i], m.get(a[i])+1);
}
else {
m.put(a[i], 1);
}
}
//重复次数
int most=0;
//遍历
Iterator iter=m.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry=(Map.Entry)iter.next();
int key=(Integer)entry.getKey();
int val=(Integer)entry.getValue();
//更新
if(val>most) {
result=key;
most=val;
}
}
return result;
}
5.4求数组中两两相加等于n的组合种数
//排序+双指针O(nlogn)
public static int findSum(int[] arr,int sum) {
Arrays.sort(arr);
int begin=0,end=arr.length-1;
//计数
int count=0;
while(begin<end) {
//小于sum则前指针后移
if((arr[begin]+arr[end])<sum)
begin++;
else if((arr[begin]+arr[end])>sum)
end--;
else {
//数组中没有相同元素才可以
count++;
begin++;
end--;
}
}
return count;
}
5.5数组循环右移k位
//方法(1)翻转最后k个元素,(2)翻转前面的剩余元素(3)整体做一次翻转
//翻转
public static void reverse(int[] arr,int start,int end) {
for(;start<end;start++,end--) {
int tmp=arr[start];
arr[start]=arr[end];
arr[end]=tmp;
}
}
private static void shift_k(int[] arr,int k) {
int n=arr.length;
//if k>n;
k=k%n;
//注意索引
reverse(arr,0,n-k-1);
reverse(arr,n-k,n-1);
reverse(arr,0,n-1);
}
5.6找出第k个最小的数
//基于快排思想做剪枝;
public static int getKMin(int[] arr,int k) {
if(arr==null || arr.length<k)
return Integer.MIN_VALUE;
return quickSort(arr,0,arr.length-1,k);
}
//快排变种只取有用的分支
public static int quickSort(int[] arr,int low,int high,int k) {
if(low>high)
return Integer.MIN_VALUE;
int tmp=arr[low];
int i=low,j=high;
//快排核心
while(i<j) {
while(i<j&&arr[j]>tmp)
j--;
while(i<j&&arr[i]<tmp)
i++;
if(i<j) {
int tmpValue=arr[i];
arr[i]=arr[j];
arr[j]=tmpValue;
}
}
arr[i]=tmp;
//剪枝(1)i+1==k命中(2)i+1<k则在右半部分递归(3)i+1>k则在左半部分递归
if(i+1==k)
return arr[i];
else if(i+1<k)
return quickSort(arr,i+1,high,k);
else
return quickSort(arr,low,i-1,k);
}
5.7数组中只出现一次的数
//只有一个数出现一次,其他两次;
//做异或,一个数异或自己得0;O(n)
//适用于重复次数为偶数
public static int findNotDouble(int a[]) {
int result=a[0];
for(int i=1;i<a.length;i++) {
result^=a[i];
}
return result;
}
//通用版:如果数组内所有数都重复n次则数组中所有数对应的二进制数中,各个位上的1出现的次数均可以被n整除;
public static int findOne(int[] array,int appearTimes) {
int[] bitCount=new int[32];
//计算数组里所有数的二进制对应统计
for(int i=0;i<array.length;i++) {
for(int j=0;j<32;j++) {
bitCount[j]+=((array[i]>>j)&1);
}
}
//若某位没被整除则肯定目标数字在这一位为1
int appearOne=0;
for(int i=0;i<32;i++) {
if(bitCount[i]%appearTimes!=0)
appearOne+=(1<<i);
}
return appearOne;
}
5.8找出数组中唯一重复的元素
问题:数组a[N],存放着1~N-1,其中某个数重复一次
(1)数学求和,因为连续,可以通过累加和求解,对数组求和然后减去1~N-1的和
(2)异或数组a[N]中的N个数异或之后再与1~N-1异或的结果做异或运算即为所求;
public static int xor_findDup(int[] a) {
int n=a.length;
int result=0;
for(int i=0;i<n;i++) {
result^=a[i];
}
for(int i=1;i<n;i++) {
result^=i;
}
return result;
}
(3)申请N-1长度的数组flag,遍历a[i],将其对应的数组flag的元素赋值为1;如果已经置过1 则结果为该数;
问题变式:取值为[1,n-1]含n个元素的整数数组,至少存在一个重复数,可能有多个;
//取反法:遍历数组元素为i,取反array[i],如果i出现两次则会进行两次取反,跟原来的值相同且是正数,若出现一次则为负数
public static int xor_findDup(int[] a) {
int n=a.length;
int result=Integer.MAX_VALUE;
for(int i=0;i<n;i++) {
if(a[i]>0)
//注意
a[a[i]]=-a[a[i]];
else
a[-a[i]]=-a[a[i]];
}
///从1 开始
for(int i=1;i<n;i++) {
if(a[i]>0)
result=i;
else
a[i]=-a[i];
}
return result;
}
5.9递归求一个整数数组的最大元素
public static int max(int m, int n) {
return m>n?m:n;
}
//递归
public static int maxNum(int[] a,int begin) {
int n=a.length-begin;
//n为1 则只剩一个元素
if(n==1) {
return a[begin];
}
else {
return max(a[begin],maxNum(a,begin+1));
}
}
5.10求数对之差的最大值
问题:数组中的一个数字减去它右边的子数组的一个数字可以得到一个差值,求所有所有可能的差值的最大值;
//动态规划:以右边某个元素的可能存在的最大差值;即用它左边的最大值减去它即可,只需要维护一个变量记录左边的最大值
public static int max(int m, int n) {
return m>n?m:n;
}
public static int getMax(int[] a) {
if(a==null||a.length<=1)
return Integer.MIN_VALUE;
//记录左边的最大值
int maxValue=a[0];
//记录差的最大值
int diff=Integer.MIN_VALUE;
for(int i=1;i<a.length;i++) {
//更新条件
diff=max(diff,maxValue-a[i]);
maxValue=max(maxValue,a[i]);
}
return diff;
}
5.11求绝对值最小的数
问题:在一个升序的序列的数组,可能有正数、0、负数,求绝对值最小的数
//三种情况
(1)第一个元素为非负数
(2)最后一个元素为非正数
(3)有正有负
public static int getMinAbsolute(int[] a) {
if(a==null||a.length<1)
return Integer.MIN_VALUE;
int length=a.length;
//第一个元素为非负数
if(a[0]>=0)
return a[0];
//最后一个元素为非正数
if(a[length-1]<=0)
return a[length-1];
int mid=0,begin=0,end=length-1;
int absMin=0;
//有正有负;二分法
while(true) {
mid=begin+(begin+end)/2;
if(a[mid]==0)
return a[mid];
//大于0;说明分界点在左半部分
else if(a[mid]>0) {
if(a[mid-1]>0)
end=mid-1;
else if(a[mid-1]==0)
return 0;
else
break;
}
//在右半部分
else {
if(a[mid+1]<0)
begin=mid+1;
else if(a[mid+1]==0)
return 0;
else
break;
}
}
//h获取正负分界点绝对值最小的值
if(a[mid]>0) {
if(a[mid]<Math.abs(a[mid-1]))
absMin=a[mid];
else
absMin=a[mid-1];
}
else {
if(Math.abs(a[mid])<a[mid+1])
absMin=a[mid];
else
absMin=a[mid+1];
}
return absMin;
}
5.12求数组中两个元素的最小距离
问题:给定一个数组,数组中含有重复元素,给出两个数n1和n2,求这两个数字在数组中所出现位置的最小距离
//遍历数组会遇到下面两种情况:
(1)遇到n1时,记录n1的对应的下标n1_index,通过求n1_index与上次遍历到的n2的下标n2_index的差;可以求出最近一次遍历到的n1和n2的距离
(2)遇到n2时,记录n2的对应的下标n1_index,通过求n2_index与上次遍历到的n1的下标n1_index的差;可以求出最近一次遍历到的n1和n2的距离
public static int minDistance(int arr[],int n1,int n2) {
if(arr==null)
return Integer.MIN_VALUE;
int n1_index=-1,n2_index=-1;
int min_dist=Integer.MIN_VALUE+1;
for(int i=0;i<arr.length;i++) {
//遇到n1
if(arr[i]==n1) {
n1_index=i;
if(n2_index>=0)
min_dist=min(Math.abs(min_dist),Math.abs(n1_index-n2_index));
}
//遇到n2rf
if(arr[i]==n2) {
n2_index=i;
if(n1_index>=0)
min_dist=min(Math.abs(min_dist),Math.abs(n2_index-n1_index));
}
}
return min_dist;
}
5.13求指定数字在数组中第一次出现的位置
问题:给定数组arr={3,4,5,6,5,6,7,8,9,8},这个数组中相邻元素之差都为1,给定数字9,它在数组中第一次 出现的下标为8;
//跳跃法:从数组特点分析;得出思路:
//从数组第一个元素开始;把数组当前位置的值与t进行比较,
//若相等则返回数组下标,否则从数组下标为i+|t-a[i]|出继续查找
//
public static int findIndex(int[] arr,int t) {
if(arr==null)
return -1;
int len=arr.length;
int i=0;
while(i<len) {
if(arr[i]==t)
return i;
else
i+=Math.abs(t-arr[i]);
}
return -1;
}
5.14对数组的两个有序字段合并
问题:数组a[0,mid-1]和a[mid,n-1]是各自有序,对其合并得到a[0,n-1]整体有序,要求空间复杂度为O(1)
public static void voidSort(int[] a,int mid) {
int tmp;
//遍历a[i](0<=i<mid)
for(int i=0;i<mid;i++) {
//条件a[mid]<a[i]然后做交换,再对a[mid]在右边数组做插入排序
if(a[mid]<a[i]) {
tmp=a[i];
a[i]=a[mid];
a[mid]=tmp;
findRightMid(a,mid);
}
}
}
//对a[mid]在右边数组做插入排序
public static void findRightMid(int[] a,int mid) {
int len=a.length;
int tmp;
for(int i=mid;i<len-1;i++) {
if(a[i+1]<a[i]) {
tmp=a[i];
a[i]=a[i+1];
a[i+1]=tmp;
}
}
}
5.14对数组的两个有序子段合并
问题:数组a[0,mid-1]和a[mid,n-1]是各自有序的,对数组a[0,n-1]的两个有序段进行合并,得到a[0,n-1]整体有序,要求空间复杂度为O(1)
思路:首先,遍历数组中下标0~mid-1的元素,将遍历到的元素的值与a[mid]进行比较,当遍历到a[i] (0<=i<=mid-1)时,如果满足a[mid]<a[i],那么交换a[i]与a[mid]的值,接着找到交换后的a[mid]在a[mid,num-1]中的具体位置(在a[mid,num-1]中进行插入排序),实现方法为:遍历a[mid~num-2],如果在a[mid+1]<a[mid]),那么交换a[mid]与a[mid+1]的位置。
public static void findRightMid(int[] a,int mid) {
int len=a.length;
int tmp;
for(int i=mid;i<len-1;i++) {
if(a[i+1]<a[i]) {
tmp=a[i];
a[i]=a[i+1];
a[i+1]=tmp;
}
}
}
public static void voidSort(int[] a,int mid) {
int tmp;
for(int i=0;i<mid;i++) {
if(a[mid]<a[i]) {
tmp=a[i];
a[i]=a[mid];
a[mid]=tmp;
findRightMid(a,mid);
}
}
}
5.15计算另两个有序数组的交集
思路:二路归并,分别从头开始遍历两个有序数组,比较当前的两个位置的值,若相等则记录下来,若前者大则前者向后遍历,若后者大则后者向后遍历
public static ArrayList<Integer> mixed(int[] array1,int[] array2){
ArrayList<Integer> mix=new ArrayList<Integer>();
int i=0,j=0;
int n1=array1.length,n2=array2.length;
while(i<n1&j<n2) {
if(array1[i]==array2[j]) {
mix.add(array1[i]);
i++;
j++;
}
else if (array1[i]>array2[j])
j++;
else
i++;
}
return mix;
}
5.16判断一个数组值是否连续
一个数组序列,元素取值可能是0~65535中的任何一个数,相同数值不会重复出现。0是例外,可以反复出现。设计一个算法当从数组序列中任意选取5个数值,判断是否连续相邻;
(1)允许乱序;
(2)0可以通配任意数值;
(3)0可以多次出现
(4)全0算连续;
思路:若没有0,则要连续最大值和最小值差距为4;若存在0,则差距小于4;否则为不相邻
public static boolean IsContinuous(int[] a) {
int n=a.length;
int min=-1,max=-1;
for(int i=0;i<n;i++) {
if(a[i]!=0) {
if(min>a[i]||min==-1)
min=a[i];
if(max<a[i]||max==-1)
max=a[i];
}
}
if(max-min>n-1)
return false;
else
return true;
}
5.17求解数组中反序对的个数
思路:分治归并;在归并排序中额外使用一个计数器来记录逆序对的个数
package List;
/**
*
* @author yxd
*输入一个数组,求这个数组的逆序对的总数
*思路:常规二次循环;时间复杂度O(N*N);
*用归并思想;来计算逆序数时间复杂度O(n*logn) 空间复杂度O(n)
*/
public class Question51 {
static int reverseCount=0;
//归并
public static void meger_sort(int a[],int begin,int end) {
if(begin<end) {
//分割的位置
int mid=(begin+end)/2;
meger_sort(a,begin,mid);
meger_sort(a,mid+1,end);
meger(a,begin,mid,end);
}
}
public static void meger(int[] a,int begin,int mid,int end) {
int i,j,k,n1,n2;
//把两个数组复制出来
n1=mid-begin+1;
n2=end-mid;
int[] L=new int[n1];
int[] R=new int[n2];
for(i=0,k=begin;i<n1;i++,k++)
L[i]=a[k];
for(i=0,k=mid+1;i<n2;i++,k++)
R[i]=a[k];
//排序,更新数组
for(k=begin,i=0,j=0;i<n1&&j<n2;k++) {
if(L[i]<=R[j])
a[k]=L[i++];
else {
//计算逆序数
//错误点注意
reverseCount+=mid-k+1;
a[k]=R[j++];
}
}
//剩余元素直接复制
if(i<n1) {
for(j=i;j<n1;j++,k++)
a[k]=L[j];
}
if(j<n2) {
for(i=j;i<n2;i++,k++)
a[k]=R[i];
}
}
public static void main(String[] args) {
int[] a= {5,8,3,6,0};
meger_sort(a,0,a.length-1);
System.out.println(reverseCount);
}
}
5.18求解最小三元组的距离
问题:已知3个升序整数数组a[l],b[m],c[n],在三个数组里各找一个元素使其组成的三元组距离最小;三元组距离:a[i],b[j],c[k]是一个三元组,那么距离为Distance=max(|a[i]-b[j]|,|a[i]-c[k]|,|b[j]-c[k]|)
思路:最小距离法:假设当前遍历到三个数组的元素为a[i],b[j],c[k]并且a[i]<=b[j]<=c[k]此时距离为D=c[k]-a[i];分类讨论三个元素后移。
得出最小的那个后移有可能出现最小距离
//求三个数的最大值
public static int max(int a,int b,int c) {
int max=a>b?a:b;
max=max>b?max:b;
return max;
}
//求三个数的最小值
public static int min(int a,int b,int c) {
int min=a<b?a:b;
min=min<b?min:b;
return min;
}
public static int minDistance(int[] a,int[] b,int[] c) {
int alen=a.length;
int blen=b.length;
int clen=c.length;
int i=0,j=0,k=0;
int curDist=0;
int min=0;
int minDist=Integer.MAX_VALUE;
while(true) {
//当前三元组的距离
curDist=max(Math.abs(a[i]-b[j]),Math.abs(a[i]-c[k]),Math.abs(b[j]-c[k]));
//更新
if(curDist<minDist)
minDist=curDist;
min=min(a[i],b[j],c[k]);
//判断哪个数组要后移
if(a[i]==min) {
if(++i>=alen)
break;
}
else if(b[j]==min) {
if(++j>=blen)
break;
}else {
if(++k>=clen)
break;
}
}
return minDist;
}
六、字符串
6.1 字符串反转
问题:把一个句子里的单词进行反转;
思路:只需要两次反转;对整个字符串反转;再对每个单词进行反转
//反转函数
public void swap(char[] cArr,int front,int end) {
while(front<end) {
char tmp=cArr[front];
cArr[front]=cArr[end];
cArr[end]=tmp;
front++;
end--;
}
}
public String swapWords(String s) {
char[] cArr=s.toCharArray();
//对整个字符串反转
swap(cArr,0,cArr.length-1);
int begin=0;
//对每个单词反转
for(int i=0;i<cArr.length;i++) {
if(cArr[i]==' ') {
swap(cArr,begin,i-1);
begin=i+1;
}
}
//最后一个单词
swap(cArr,begin,cArr.length-1);
return new String(cArr);
}
6.2 判断两个字符串是否由相同的字符组成
问题:由相同的字符组成是指两个字符串的字母以及各个字母的个数是一样的;
思路:可以双重循环排序;也可以牺牲空间,由于ASCII一共256个字符可以通过申请256大小的数组记录各个字符出现的次数;遍历第一个字符串,将字符串中字符对应的ASCII码值作为数组下标,对应数组元素加1,遍历第二个字符串时减1,看最后是否全为0;
public boolean compare(String s1,String s2) {
byte[] b1=s1.getBytes();
byte[] b2=s2.getBytes();
int[] bCount=new int[256];
//初始化
for(int i=0;i<256;i++)
bCount[i]=0;
//遍历第一个字符串
for(int i=0;i<s1.length();i++)
bCount[i-'0']++;
//遍历第二个字符串
for(int i=0;i<s2.length();i++)
bCount[i-'0']--;
//判断
for(int i=0;i<256;i++) {
if(bCount[i]!=0)
return false;
}
return true;
}
6.3 删除字符串中重复的字符
思路:由于常见字符有256个;则需要申请256个大小的空间,1bit就可以记录一个字符是否出现;因此可以申请大小为8的int类型数组;int类型占32bit;
public static String removeDuplicate(String str) {
char[] c=str.toCharArray();
int len=c.length;
int[] flags=new int[8];
//初始化
for(int i=0;i<8;i++)
flags[i]=0;
for(int i=0;i<len;i++) {
//字符在数组的位置
int index=(int)c[i]/32;
//字符在该位置的具体偏移
int shift=(int)c[i]%32;
//判断该位置是否不等于0;也就是出现过
if((flags[index]&1<<shift)!=0)
c[i]='\0';
//更新
flags[index]|=1<<shift;
}
//更新
int l=0;
for(int i=0;i<len;i++) {
if(c[i]!='\0')
c[l++]=c[i];
}
return new String(c,0,l);
}
6.4 统计一行字符的单词数
思路:判断单词条件,前一个字符是空格,后一个不是;
public static int worldCount(String s) {
//word=0表示前一个字符是空格
int word=0;
int count=0;
for(int i=0;i<s.length();i++) {
if(s.charAt(i)==' ')
word=0;
else if(word==0) {
word=1;
count++;
}
}
return count;
}
6.5 按要求打印数组的排列
问题:针对1、2、2、3、4、5这六个数字,打印所有不同的排列,要求“4”不能在第三位,“3”与“5”不能相连;
思路:转换为图的遍历;六个结点构成无向图;“3”与“5”不连通;
步骤:
(1)构建无向图
(2)分别从这六个结点做图的深度优先遍历;有Set集合去掉重复
private int[] numbers=new int[] {1,2,2,3,4,5};
private int n=numbers.length;
//标记图中结点是否被访问过
private boolean[] visited=new boolean[n];
//图的二维数组表示
private int[][] graph=new int[n][n];
//数字组合
private String combination="";
public Set<String> getAllCombinations(){
//构造图
buildGraph();
Set<String> set=new HashSet<String>();
//分别从不同的结点出发深度遍历
for(int i=0;i<n;i++) {
this.depthFirstSearch(i,set);
}
return set;
}
//图初始化
private void buildGraph() {
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
if(i==j)
graph[i][j]=0;
else
graph[i][j]=1;
}
}
//确保3和5不可达
graph[3][5]=0;
graph[5][3]=0;
}
private void depthFirstSearch(int start,Set<String> set) {
//设置为已访问
visited[start]=true;
combination=combination+numbers[start];
//组合完毕;
if(combination.length()==n) {
if(combination.indexOf("4")!=2)
set.add(combination);
//不可以return
}
//递归
for(int j=0;j<n;j++) {
//如果图可达且没被访问过
if(graph[start][j]==1&&visited[j]==false)
depthFirstSearch(j,set);
}
//退回到上一步的状态
combination=combination.substring(0,combination.length()-1);
visited[start]=false;
}
public static void main(String[] args) {
CharTest t=new CharTest();
Set<String> set=t.getAllCombinations();
Iterator<String> it=set.iterator();
while(it.hasNext()) {
String string=(String)it.next();
System.out.println(string);
}
}
6.6 输出字符串的所有组合
问题:假设字符串的字符不重复,输出字符串的所有组合
思路(1):递归,遍历字符串,每个字符后取或者不取,若取就放到结果字符串里
public static void Combine(char[] c,int begin,int len,StringBuffer sb) {
if(len==0) {
System.out.print(sb+" ");
return ;
}
if(begin==c.length)
return ;
//有这个字符
sb.append(c[begin]);
Combine(c,begin,len-1,sb);
//没有这个字符
sb.deleteCharAt(sb.length()-1);
Combine(c,begin+1,len,sb);
}
public static void main(String[] args) {
String s="abc";
char[] c=s.toCharArray();
StringBuffer sb=new StringBuffer("");
int len=c.length;
//输出各种长度的字符串
for(int i=1;i<=len;i++)
Combine(c,0,i,sb);
}
《重点》思路(2):构造一个长度为n的二进制字符串,表示结果中是否包含某个字符串;例子“001”表示不含a,b有c;
public static void Combinetwo(char[] c) {
if(c==null)
return;
int len=c.length;
//是否含有某个字符
boolean used[]=new boolean[len];
//存放一次遍历的结果字符串
char cache[]=new char[len];
int result=len;
while(true) {
int index=0;
while(used[index]) {
used[index]=false;
++result;
//退出条件
if(++index==len)
return;
}
used[index]=true;
cache[--result]=c[index];
System.out.print(new String(cache).substring(result)+" ");
}
}
七、树
7.1实现二叉排序树
//数据结构
class Node{
public int data;
public Node left;
public Node right;
public int leftMaxDistance;
public int rightMaxDistance;
public Node(int data) {
this.data=data;
this.left=null;
this.right=null;
}
}
public class BinaryTree {
private Node root;
public BinaryTree() {
root=null;
}
//插入元素到二叉树中
public void insert(int data) {
Node newNode=new Node(data);
if(root==null)
root=newNode;
else {
Node current=root;
Node parent;
//寻找插入位置
while(true) {
parent=current;
if(data<current.data) {
current=current.left;
if(current==null) {
parent.left=newNode;
return;
}
}
else {
current=current.right;
if(current==null) {
parent.right=newNode;
return;
}
}
}
}
}
//构建二叉树
public void buildTree(int[] data) {
for(int i=0;i<data.length;i++)
insert(data[i]);
}
//递归先序遍历
public void inOrder(Node localRoot) {
if(localRoot!=null) {
inOrder(localRoot.left);
System.out.print(localRoot.data+" ");
inOrder(localRoot.right);
}
}
}
7.2层序遍历
队列实现
public void layerTranverse() {
if(this.root==null)
return ;
Queue<Node> q=new LinkedList<Node>();
q.add(this.root);
while(!q.isEmpty()) {
Node n=q.poll();
System.out.print(n.data+" ");
if(n.left!=null)
q.add(n.left);
if(n.right!=null)
q.add(n.right);
}
}
7.3由先序遍历和中序遍历求后序遍历
步骤:
(1)确定树的根节点,树根是当前树中所有元素在先序遍历最先出现的元素;
(2)求解树的子树,找到根在中序遍历的位置,位置左边就是二叉树的左孩子,位置右边就是二叉树的右孩子;,若有根节点左右都为空则根节点为叶子结点;
(3)对二叉树的左右孩子分别进行步骤(1)(2)直到求出二叉树为止
//后序遍历
public void postOrder(Node localRoot) {
if(localRoot!=null) {
postOrder(localRoot.left);
System.out.print(localRoot.data+" ");
postOrder(localRoot.right);
}
}
public void postOrder() {
this.postOrder(this.root);
}
public void initTree(int[] preOrder,int[] inOrder) {
this.root=this.initTree(preOrder, 0,preOrder.length-1,inOrder,0,inOrder.length-1);
}
//构建树
public Node initTree(int[] preOrder,int start1,int end1,int[] inOrder,int start2,int end2) {
if(start1>end1||start2>end2)
return null ;
//根节点
int rootData=preOrder[start1];
Node head=new Node(rootData);
//找到根节点的位置
int rootIndex=findIndex(inOrder,rootData,start2,end2);
int offSet=rootIndex-start2-1;
//构建左子树
Node left=initTree(preOrder,start1+1,start1+1+offSet,inOrder,start2,start2+offSet);
//构建右子树
Node right=initTree(preOrder,start1+offSet+2,end1,inOrder,rootIndex+1,end2);
head.left=left;
head.right=right;
return head;
}
public int findIndex(int[] a,int x,int begin,int end) {
for(int i=0;i<=end;i++) {
if(a[i]==x)
return i;
}
return -1;
}
7.4 求二叉树的结点的最大距离
问题描述:节点距离指这两个结点之间边的个数
思路:求左子树距离根节点的最大距离记作leftMaxDistance;求右子树距离根节点的最大距离记作rightMaxDistance;那么二叉树中结点的最大距离maxDistance满足maxDistance=leftMaxDistance+rightMaxDistance;
class Node{
public int data;
public Node left;
public Node right;
public int leftMaxDistance;
public int rightMaxDistance;
public Node(int data) {
this.data=data;
this.left=null;
this.right=null;
}
}
public class BinaryTree {
private Node root;
//记录最大距离
private int maxLen=0;
public BinaryTree() {
root=null;
}
public int max(int a,int b) {
return a>b?a:b;
}
public void findMaxDistance(Node root) {
if(root==null)
return ;
//计算左子树距离根节点的最大距离
if(root.left==null)
root.leftMaxDistance=0;
else {
findMaxDistance(root.left);
root.leftMaxDistance=max(root.left.rightMaxDistance,root.left.leftMaxDistance)+1;
}
//计算右子树距离根节点的最大距离
if(root.right==null)
root.rightMaxDistance=0;
else {
findMaxDistance(root.right);
root.rightMaxDistance=max(root.right.leftMaxDistance,root.right.rightMaxDistance)+1;
}
//更新
if(root.leftMaxDistance+root.rightMaxDistance>maxLen)
maxLen=root.leftMaxDistance+root.rightMaxDistance;
}
最后打个广告:下面给大家打个广告哈哈哈:给大家介绍一个公众号,亲测方便好用。主要功能是搜索最优优惠券,购物返利,但是不仅仅包括京东、淘宝、拼多多等电商购物会返利,包括滴滴打车、美团外卖以及美团优惠券等等都会有返利,并且返利是我见过最高的。反正用到就是赚到,绝对亏不了,能省一点是一点。而且现在还接入了最火热的chatGPT,没事的时候还可以和人工智能聊天,爽歪歪。我不允许你不知道这么牛的东西。哈哈哈,二维码放到下面了,微信扫一扫试用一下。