剑指Offer(java答案)

剑指Offer(java答案)


文章目录

生产者/消费者

public class Main {
    class Message<T> {
        private T message;
        public Message(T message) {
            this.message = message;
        }
    }

    /**
     * 消息队列
     */
    class MessageQueue {
        private LinkedList<Message> queue;
        private int capacity;

        public MessageQueue(int capacity) {
            this.capacity = capacity;
            queue = new LinkedList<>();
        }

        public Message take() {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    //没货了, wait
                    queue.wait();
                }
                Message message = queue.removeFirst();
                queue.notifyAll();
                return message;
            }
        }

        public void put(Message message) {
            synchronized (queue) {
                while (queue.size() == capacity) {
                    //库存已达上限, wait
                    queue.wait();
                }
                queue.addLast(message);
                queue.notifyAll();
            }
        }
    }

    /**
     * 应用
     */
    public void main() {
        MessageQueue messageQueue = new MessageQueue(2);
        // 4 个生产者线程, 下载任务
        for (int i = 0; i < 4; i++) {
            int id = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 生产任务
                    messageQueue.put(new Message("111"));
                }
            }, "生产者" + i).start();
        }
        // 1 个消费者线程, 处理结果
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    // 消费任务
                    Message message = messageQueue.take();
                }
            }
        }, "消费者").start();
    }
}

3个线程,交替打印,abcabcabcabcabc

线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现

class SyncWaitNotify {
        private int flag;
        private int loopNumber;

        public SyncWaitNotify(int flag, int loopNumber) {
            this.flag = flag;
            this.loopNumber = loopNumber;
        }

        public void print(int waitFlag, int nextFlag, String str) {
            for (int i = 0; i < loopNumber; i++) {
                synchronized (this) {
                    while (this.flag != waitFlag) {
                        this.wait();
                    }
                    System.out.print(str);
                    flag = nextFlag;
                    this.notifyAll();
                }
            }
        }

        /**
         * 应用
         */
        public void main() {
            SyncWaitNotify syncWaitNotify = new SyncWaitNotify(1, 5);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    syncWaitNotify.print(1, 2, "a");
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    syncWaitNotify.print(2, 3, "b");
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    syncWaitNotify.print(3, 1, "c");
                }
            }).start();
        }
    }

自定义线程池

/**
     * 1、拒绝策略
     */
    interface RejectPolicy<T> {
        void reject(BlockingQueue<T> queue, T task);
    }

    /**
     * 2、任务队列
     */
    class BlockingQueue<T> {
        // 1. 任务队列
        private Deque<T> queue = new ArrayDeque<>();
        // 2. 锁
        private ReentrantLock lock = new ReentrantLock();
        // 3. 生产者条件变量
        private Condition fullWaitSet = lock.newCondition();
        // 4. 消费者条件变量
        private Condition emptyWaitSet = lock.newCondition();
        // 5. 容量
        private int capcity;

        public BlockingQueue(int capcity) {
            this.capcity = capcity;
        }

        // 阻塞获取
        public T take() {
            lock.lock();
            try {
                while (queue.isEmpty()) {
                    emptyWaitSet.await();
                }
                T t = queue.removeFirst();
                fullWaitSet.signal();
                return t;
            } finally {
                lock.unlock();
            }
        }

        // 阻塞添加
        public void put(T task) {
            lock.lock();
            try {
                while (queue.size() == capcity) {
                    //等待加入任务队列
                    fullWaitSet.await();
                }
                //加入任务队列
                queue.addLast(task);
                emptyWaitSet.signal();
            } finally {
                lock.unlock();
            }
        }

        // 带超时阻塞获取
        public T poll(long timeout, TimeUnit unit) {
            lock.lock();
            try {
                // 将 timeout 统一转换为 纳秒
                long nanos = unit.toNanos(timeout);
                while (queue.isEmpty()) {
                    // 返回值是剩余时间
                    if (nanos <= 0) {
                        return null;
                    }
                    nanos = emptyWaitSet.awaitNanos(nanos);
                }
                T t = queue.removeFirst();
                fullWaitSet.signal();
                return t;
            } finally {
                lock.unlock();
            }
        }

        // 带超时时间阻塞添加
        public boolean offer(T task, long timeout, TimeUnit timeUnit) {
            lock.lock();
            try {
                long nanos = timeUnit.toNanos(timeout);
                while (queue.size() == capcity) {
                    if (nanos <= 0) {
                        return false;
                    }
                    //等待加入任务队列
                    nanos = fullWaitSet.awaitNanos(nanos);
                }
                //加入任务队列
                queue.addLast(task);
                emptyWaitSet.signal();
                return true;
            } finally {
                lock.unlock();
            }
        }

        public int size() {
            lock.lock();
            try {
                return queue.size();
            } finally {
                lock.unlock();
            }
        }

        public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
            lock.lock();
            try {
                // 判断队列是否满
                if (queue.size() == capcity) {
                    rejectPolicy.reject(this, task);
                } else { // 有空闲
                    //加入任务队列
                    queue.addLast(task);
                    emptyWaitSet.signal();
                }
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 线程池
     */
    class ThreadPool {
        // 任务队列
        private BlockingQueue<Runnable> taskQueue;
        // 线程集合
        private HashSet<Worker> workers = new HashSet<>();
        // 核心线程数
        private int coreSize;
        // 获取任务时的超时时间
        private long timeout;
        private TimeUnit timeUnit;
        private RejectPolicy<Runnable> rejectPolicy;

        public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity,
                          RejectPolicy<Runnable> rejectPolicy) {
            this.coreSize = coreSize;
            this.timeout = timeout;
            this.timeUnit = timeUnit;
            this.taskQueue = new BlockingQueue<>(queueCapcity);
            this.rejectPolicy = rejectPolicy;
        }

        // 执行任务
        public void execute(Runnable task) {
            // 当任务数没有超过 coreSize 时,直接交给 worker 对象执行
            // 如果任务数超过 coreSize 时,加入任务队列暂存
            synchronized (workers) {
                if (workers.size() < coreSize) {
                    Worker worker = new Worker(task);
                    workers.add(worker);
                    worker.start();
                } else {
                    taskQueue.tryPut(rejectPolicy, task);
                }
            }
        }

        class Worker extends Thread {
            private Runnable task;

            public Worker(Runnable task) {
                this.task = task;
            }

            @Override
            public void run() {
                // 执行任务
                // 1) 当 task 不为空,执行任务
                // 2) 当 task 执行完毕,再接着从任务队列获取任务并执行
                // while(task != null || (task = taskQueue.take()) != null) {
                while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                    task.run();
                    task = null;
                }
                synchronized (workers) {
                    workers.remove(this);
                }
            }
        }
    }

3、二维数组中的查找

题目描述
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
| 1| 2| 8|9|
|-|-----| -----|
| 2 | 4 | 9|12|
| 4| 7| 10 |13|
| 6 | 8 | 11 |15|

思路: 从右上角开始,若小,向下走,删除一行,若大,向左走,删除一列

/*
利用二维数组由上到下,由左到右递增的规律,
那么选取右上角或者左下角的元素a[row] [col]与target进行比较,
当target小于元素a[row] [col]时,那么target必定在元素a所在行的左边,
即col--;
当target大于元素a[row][col]时,那么target必定在元素a所在列的下边,
即row++;
*/
public class Solution {
    public boolean Find(int [][] array,int target) {
        int row=0;
        int col=array[0].length-1;
        while(row<=array.length-1&&col>=0){
            if(target==array[row][col])
                return true;
            else if(target>array[row][col])
                row++;
            else
                col--;
        }
        return false; 
    }
}

4、替换空格

题目描述
请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:先遍历一遍字符,统计空格数,由此计算替换之后的总长度。然后从后往前加载字符串,如果发现空格,就替换20%

/*
问题1:替换字符串,是在原来的字符串上做替换,还是新开辟一个字符串做替换!
问题2:在当前字符串替换,怎么替换才更有效率(不考虑java里现有的replace方法)。
      从前往后替换,后面的字符要不断往后移动,要多次移动,所以效率低下
      从后往前,先计算需要多少空间,然后从后往前移动,则每个字符只为移动一次,这样效率更高一点。
*/
public class Solution {
    public String replaceSpace(StringBuffer str) {
        int spacenum = 0;//spacenum为计算空格数
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)==' ')
                spacenum++;
        }
        int indexold = str.length()-1; //indexold为为替换前的str下标
        int newlength = str.length() + spacenum*2;//计算空格转换成%20之后的str长度
        int indexnew = newlength-1;//indexold为为把空格替换为%20后的str下标
        str.setLength(newlength);//使str的长度扩大到转换成%20之后的长度,防止下标越界
        for(;indexold>=0 && indexold<newlength;--indexold){ 
                if(str.charAt(indexold) == ' '){  //
                str.setCharAt(indexnew--, '0');
                str.setCharAt(indexnew--, '2');
                str.setCharAt(indexnew--, '%');
                }else{
                    str.setCharAt(indexnew--, str.charAt(indexold));
                }
        }
        return str.toString();
    }
}

5、从尾到头打印链表

题目描述:
输入一个链表,从尾到头打印链表每个节点的值。

思路1:栈

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*/
import java.util.Stack;
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode == null){
            ArrayList list = new ArrayList();
            return list;
        }
        Stack<Integer> stk = new Stack<Integer>();
        while(listNode != null){
            stk.push(listNode.val);
            listNode = listNode.next;
        }
        ArrayList<Integer> arr = new ArrayList<Integer>();
        while(!stk.isEmpty()){
            arr.add(stk.pop());
        }
        return arr;
    }
}

思路2:递归

public class Solution {
    public void printListFromTailToHead(ListNode listNode) {
      if(listNode != null){
            if(listNode.next != null){
                printListFromTailToHead(listNode.next);
            }
           System.out.print(""+listNode.var);
        }
 
    }
}

6、重建二叉树

题目描述:已知中序和前序,请重建二叉树

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        TreeNode root=reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
        return root;
    }
    //前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
    private TreeNode reConstructBinaryTree(int [] pre,int startPre,int endPre,int [] in,int startIn,int endIn) {  
        if(startPre>endPre||startIn>endIn)
            return null;
        TreeNode root=new TreeNode(pre[startPre]);
        for(int i=startIn;i<=endIn;i++)
            if(in[i]==pre[startPre]){
                root.left=reConstructBinaryTree(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);  //注意pre的位置,要用偏移量,不能用i,因为i是在变化              
                root.right=reConstructBinaryTree(pre,i-startIn+startPre+1,endPre,in,i+1,endIn);
            }                 
        return root;
    }
}

引申:已知中序和后序求前序

package com.zhuang.tree;

public class Main {
	
	 public static class TreeNode {
	      int val;
	      TreeNode left;
	      TreeNode right;
	      TreeNode(int x) { val = x; }
	  }
	
	 public static TreeNode reConstructBinaryTree(int [] post,int [] in) {
	        TreeNode root=reConstructBinaryTree(post,0,post.length-1,in,0,in.length-1);
	        return root;
	    }
	 
	 
	    private static TreeNode reConstructBinaryTree(int [] post,int startPost,int endPost,int [] in,int startIn,int endIn) {
	         
	        if(startPost>endPost||startIn>endIn)
	            return null;
	        
	        TreeNode root=new TreeNode(post[endPost]);
	         
	        for(int i=startIn;i<=endIn;i++)
	            if(in[i]==post[endPost]){
	                root.left=reConstructBinaryTree(post,startPost,startPost+i-startIn-1,in,startIn,i-1);
	                root.right=reConstructBinaryTree(post,startPost+i-startIn,endPost-1,in,i+1,endIn);
	            }
	                 
	        return root;
	    }
	    
	    public static void preOrder(TreeNode root){
	    	if(root == null){
	    		return;
	    	}
	    	System.out.println(root.val);
	    	preOrder(root.left);
	    	preOrder(root.right);
	    }
	    
	    public static void main(String[] args){
	    	int[] post = {2,4,3,1,6,7,5};
	    	int[] in = {1,2,3,4,5,6,7};
	    	TreeNode root = reConstructBinaryTree(post, in);
	    	preOrder(root);
	    }
}

7、用两个栈实现队列

题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

思路:入栈给stack1,出栈时,若stack2不为空,则出栈,若为空,把stack1的内容全都放入stack2,然后再出栈

import java.util.Stack;
 
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
     
   public void push(int node) {
        stack1.push(node);
    }
     
     
   public int pop() {
        
       while(!stack2.isEmpty())
        {
            return stack2.pop();
        }
         
        while(!stack1.isEmpty())
        {
            stack2.push(stack1.pop());
        }
        
        return stack2.pop();
    }
}

8、旋转数组的最小数字

题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

import java.util.ArrayList;

public class Solution {
	/*
	 * 传进去旋转数组,注意旋转数组的特性: 1.包含两个有序序列 2.最小数一定位于第二个序列的开头 3.前序列的值都>=后序列的值
	 */

	// 用到了快速排序的快速定位范围的思想,
	public int minNumberInRotateArray(int[] array) {

		if (array == null || array.length == 0) {
			return 0;
		}
		int low = 0;//指向第一个
		int up = array.length - 1;//指向最后一个
		int mid = low;

		// 当low和up两个指针相邻时候,就找到了最小值,也就是
		// 右边序列的第一个值

		while (array[low] >= array[up]) {
			if (up - low == 1) {
				mid = up;
				break;
			}
			// 如果low、up、mid下标所指的值恰巧相等
			// 如:{0,1,1,1,1}的旋转数组{1,1,1,0,1}
			if (array[low] == array[up] && array[mid] == array[low])
				return MinInOrder(array);
			mid = (low + up) / 2;
			// 这种情况,array[mid]仍然在左边序列中
			if (array[mid] >= array[low])
				low = mid;// 注意,不能写成low=mid+1;
			// 要是这种情况,array[mid]仍然在右边序列中
			else if (array[mid] <= array[up])
				up = mid;
		}

		return array[mid];

	}

	private int MinInOrder(int[] array) {
		// TODO Auto-generated method stub
		int min = array[0];
		for (int i = 1; i < array.length; i++) {
			if (array[i] < min) {
				min = array[i];

			}
		}
		return min;
	}

}

9、斐波那契数列

题目描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。

思路1:递归,简洁但效率不高

public int Fibonacci(int n) {        
        if(n<=0)
            return 0;
        if(n==1)
            return 1;
        return Fibonacci(n-1) + Fibonacci(n-2); 
    }

思路2:循环,O(N)

public class Solution {
    public int Fibonacci(int n) {
        int preNum=1;
        int prePreNum=0;
        int result=0;
        if(n==0)
            return 0;
        if(n==1)
            return 1;
        for(int i=2;i<=n;i++){
            result=preNum+prePreNum;
            prePreNum=preNum;
            preNum=result;
        }
        return result;
 
    }
}

扩展1:跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

思路:斐波拉契数序列,初始条件n=1:只能一种方法,n=2:两种
对于第n个台阶来说,只能从n-1或者n-2的台阶跳上来,所以
F(n) = F(n-1) + F(n-2)

扩展2:变态跳台阶

题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

思路:
因为n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
所以f(n)=f(n-1)+f(n-2)+…+f(1)
因为f(n-1)=f(n-2)+f(n-3)+…+f(1)
所以f(n)=2*f(n-1)
所以f(n)=2的(n-1)次幂

public class Solution {
    public int JumpFloorII(int target) {
        if(target<=0)
            return 0;
        return 1<<(target-1);
    }
}

10、二进制中1的个数

题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

最差的解法:因为要考虑负数的问题,若是负数,因为要保证一直输负数,多以第一位一直为1,最后会变成0XFFFFFFFF,造成死循环

public class Solution {
public int  NumberOf1(int n) {
        int count= 0;
        int flag = 1;
        while (n!= 0){
            if ((n & 1) != 0){
                count++;      
            }
            n = n>>1;
        }
         return count;
     }    
}

改进的解法:

public class Solution {
public int  NumberOf1(int n) {
        int count= 0;
        int flag = 1;
        while (flag != 0){
            if ((n & flag) != 0){
                count++;      
            }
            flag  = flag << 1;
        }
         return count;
     }    
}

最精妙的解法:

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n!= 0){
            count++;
            n = n & (n - 1);//每一次将最后一个1变成0
         }
        return count;
    }
}

11、数值的整数次方

题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

**思路1:**本题主要考虑边界问题,全面不够高效的解法,注意:由于计算机表示小数(包括float和double型小数)都有误差,我们不能直接用==判断两个小数是否相等,如果两个小数的差的绝对值很小,比如小于0.0000001,就可以认为他们相等

public class Solution {
    public double Power(double base, int exponent) {
        double res = 0.0;
        if (equal(base, 0.0) && exponent < 0) {
            throw new RuntimeException("0的负数次幂没有意义");
        }
        // 这里定义0的0次方为1
        if (exponent == 0) {
            return 1.0;
        }
        if (exponent < 0) {
            res = powerWithExponent(1.0/base, -exponent);
        } else {
            res = powerWithExponent(base, exponent);
        }
        return res;
    }
     
    private double powerWithExponent(double base, int exponent) {
        double res = 1.0;
        for (int i = 1; i <= exponent; i++) {
            res = res * base;
        }
        return res;
    }
     
    // 判断double类型的数据
    private boolean equal(double num1, double num2) {
        if (Math.abs(num1 - num2) < 0.0000001) {
            return true;
        }
        return false;
    }
}

思路2:n为偶数时:an=an/2 * a^n/2;
n为奇数,an=(a(n-1)/2)* (a^(n-1/2))* a
所以对乘法处进行优化,如果是32次方,等于16次方*16次方

 /*
    ** 对乘法进行优化的部分
    */
    private double powerWithExponent(double base, int exponent) {
        if(exponent==0){
            return 1;
        }
         
        if(exponent==1){
            return base;
        }
         
       double result = powerWithExponent(base,exponent>>1);//每次除以2
       result*=result;//最后相乘,如果是奇数,还要乘一个
        
        //如果是奇数次方的情况,最终除2余1要与base相乘
        if((exponent & 0x1)==1){
            result *= base;
        }
        return result;
    }

12、打印1到最大的n位数

题目描述:如n=3,则从1打印到999

public class Solution {

	// ====================方法一====================
	public static void Print1ToMaxOfNDigits(int n) {
		if (n <= 0)
			return;

		char[] number = new char[n];
		
		//每一个字符设为0
		for (int i = 0; i < n; i++) {
			number[i] = '0';
		}

		while (!Increment(number)) {//如果加法溢出,则退出,否则打印数字
			PrintNumber(number);
		}

	}

	// 字符串number表示一个数字,在 number上增加1
	// 如果做加法溢出,则返回true;否则为false
	public static boolean Increment(char[] number) {
		boolean isOverflow = false;//溢出标志
		int nTakeOver = 0;//进位
		int nLength = number.length;

		for (int i = nLength - 1; i >= 0; i--) {//从后向前,最后一位数字加1
			int nSum = number[i] - '0' + nTakeOver;
			if (i == nLength - 1)
				nSum++;

			if (nSum >= 10) {
				if (i == 0)
					isOverflow = true;
				else {
					nSum -= 10;
					nTakeOver = 1;
					number[i] = (char) ('0' + nSum);
				}
			} else {
				number[i] = (char) ('0' + nSum);
				break;
			}
		}

		return isOverflow;
	}

	// 字符串number表示一个数字,数字有若干个0开头
	// 打印出这个数字,并忽略开头的0
	public static void PrintNumber(char[] number) {
		boolean isBeginning0 = true;
		int nLength = number.length;

		//标志位的思想,从第一位不为0的数字开始打印,如000123,打印123
		for (int i = 0; i < nLength; ++i) {
			if (isBeginning0 && number[i] != '0')
				isBeginning0 = false;

			if (!isBeginning0) {
				System.out.print(number[i]);
			}
		}
		System.out.println();
	}
}

思路2:用递归,代码简洁,思路不好想,每一位都是从0到9的全排列

public class Solution {

	// // ====================方法二:递归====================
	public static void Print1ToMaxOfNDigits(int n) {
		if (n <= 0)
			return;

		char[] number = new char[n];

		for (int i = 0; i < 10; ++i) {
			number[0] = (char) (i + '0');
			Print1ToMaxOfNDigitsRecursively(number, n, 0);
		}

	}

	public static void Print1ToMaxOfNDigitsRecursively(char[] number, int length,int index) {
		if (index == length - 1) {
			PrintNumber(number);
			return;
		}

		for (int i = 0; i < 10; ++i) {
			number[index + 1] = (char) (i + '0');
			Print1ToMaxOfNDigitsRecursively(number, length, index + 1);
		}
	}

	// 字符串number表示一个数字,数字有若干个0开头
	// 打印出这个数字,并忽略开头的0
	public static void PrintNumber(char[] number) {
		boolean isBeginning0 = true;
		int nLength = number.length;

		// 标志位的思想,从第一位不为0的数字开始打印,如000123,打印123
		for (int i = 0; i < nLength; ++i) {
			if (isBeginning0 && number[i] != '0')
				isBeginning0 = false;

			if (!isBeginning0) {
				System.out.print(number[i]);
			}
		}
		System.out.println();
	}
}

13、在O(1)时间删除链表结点

给定单向链表头指针和一个节点指针,在O(1)时间删除链表结点

/*
	对于删除节点,我们普通的思路就是让该结点的前一个节点指向改节点的下一个节点
	*/
	public void delete(Node head, Node toDelete){
	    if(toDelete == null){
	        return ;
	    }
	    if(toDelete.next != null){//删除的节点不是尾节点
	        toDelete.val = toDelete.next.val;
	        toDelete.next = toDelete.next.next;
	    }else if(head == toDelete){//链表只有一个节点,删除头结点也是尾节点
	    	head = null;
	    }else{ //删除的节点是尾节点的情况
	        Node node = head;
	        while(node.next != toDelete){//找到倒数第二个节点
	            node = node.next;
	        }
	        node.next = null;
	    }
	}

14、调整数组顺序使奇数位于偶数前面

题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

public class Solution {
    public void reOrderArray(int [] array) {
        //注释的部分使用快速排序的算法,很明显快速排序是不稳定的,这里需要用归并排序
        /*
        if(array.length == 0){
            return;
        }
        int high = array.length - 1;
        int low = 0;
        while(low < high){
            while(low < high && array[low] % 2 == 1){
                low ++;
            }
            while(low < high && array[high] % 2 == 0){
                high --;
            }
            int temp = array[low];
            array[low] = array[high];
            array[high] = temp;
        }*/
        
        //用用归并排序的思想,因为归并排序是稳定的
        int length = array.length;
        if(length == 0){
            return;
        }
        int[] des = new int[length];
        MergeMethod(array, des, 0,length - 1);
    }
    public void MergeMethod(int[] array, int [] des, int start, int end){
        if(start < end){
            int mid = (start + end) / 2;
            MergeMethod(array, des, start, mid);
            MergeMethod(array, des, mid + 1, end);
            Merge(array, des, start, mid, end);
        }
    }
    
    public void Merge(int[] array, int[] des, int start, int mid, int end){
        int i = start;
        int j = mid + 1;
        int k = start;
        while(i <= mid && array[i] % 2 == 1){
            des[k++] = array[i++];
        }
        while(j <= end && array[j] % 2 == 1){
            des[k++] = array[j++];
        }
        while(i <= mid){
            des[k++] = array[i++];
        }
        while(j <= end){
            des[k++] = array[j++];
        }
        
        for(int m = start; m <= end; m++){
            array[m] = des[m];
        }
    }
}

15、链表中倒数第k个结点

题目描述
输入一个链表,输出该链表中倒数第k个结点。

思路:两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指正走(k-1)步,到达第k个节点。然后两个指针同时往后移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了

/**
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head==null||k<=0){
            return null;
        }
        ListNode pre=head;
        ListNode last=head;       
        for(int i=1;i<k;i++){
            if(pre.next!=null){
                pre=pre.next;
            }else{
                return null;
            }
        }
        while(pre.next!=null){
            pre = pre.next;
            last=last.next;
        }
        return last;
    }
}

16、反转链表

题目描述
输入一个链表,反转链表后,输出链表的所有元素。

/*
 public class ListNode {
 int val;
 ListNode next = null;
 
 ListNode(int val) {
 this.val = val;
 }
 }*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null)
            return null;
        if (head.next == null)
            return head;
 
        ListNode pPre = null;
        ListNode p = head;
        ListNode pNext = head.next;
        ListNode newHead = null;
 
        while (p != null) {
            pNext = p.next;//一定要记录下来后面的节点
            if (pNext == null)
                newHead = p;
            p.next = pPre;//这里的方向已经转变
            pPre = p;
            p = pNext;//将保存的后面的节点作为下一次循环的p
 
        }
        return newHead;
 
    }
}

双向链表反转

public class LinkedNode<T>{
        private T value;
        private LinkedNode<T> prev;
        private LinkedNode<T> next;

        public LinkedNode(T value, LinkedNode<T> prev, LinkedNode<T> next) {
            super();
            this.value = value;
            this.prev = prev;
            this.next = next;
        }
        // get、set方法省略
    }

    public class LinkedList<T>{
        private transient int size=0;
        private transient LinkedNode<T> first=null;
        private transient LinkedNode<T> last=null;

        /**
         * 添加元素到双向链表头部
         * */
        public void addFirst(T t){
            LinkedNode<T> oldFirst = first;
            LinkedNode<T> newNode = new LinkedNode<T>(t,  null,oldFirst);
            first = newNode;

            if (oldFirst == null)
                last = newNode;
            else
                oldFirst.setPrev(newNode);

            size++;
        }

        /**
         * 将双向链表转化为一元数组:從頭開始循環到尾部。
         * */
        public Object[] toArray() {
            Object[] result = new Object[size];
            int i = 0;
            for (LinkedNode<T> node = first; node != null; node = node.getNext())
                result[i++] = node.getValue();
            return result;
        }

        /**
         * 反轉
         * */
        public void reverse(){
            if(first==null||last==null)
                return;

            LinkedNode<T> prev;
            LinkedNode<T> next;
            LinkedNode<T> newFirst=null;
            LinkedNode<T> newLast=null;
            for (LinkedNode<T> node = first; node != null; node = node.getPrev()){
                prev= node.getPrev();
                next= node.getNext();

                if(node.getPrev()==null){
                    newLast=node;
                }else if(node.getNext()==null){
                    newFirst=node;
                }

                node.setNext(prev);
                node.setPrev(next);
            }

            first=newFirst;
            last=newLast;
        }
    }

17、合并两个排序的链表

题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

/**
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
     public ListNode Merge(ListNode list1, ListNode list2) {
         if(list1==null)
            return list2;
         else if(list2==null)
            return list1;
         ListNode mergeHead=null;
         if(list1.val<list2.val){
             mergeHead=list1;
             mergeHead.next=Merge(list1.next, list2);
         }
         else{
             mergeHead=list2;
             mergeHead.next=Merge(list1, list2.next);
         }
         return mergeHead;

    }
}

非递归方法:

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null&&list2==null)
            return null;
        if(list1==null&&list2!=null)
            return list2;
        if(list1!=null&&list2==null)
            return list1;
        ListNode head = null;
        if(list1.val<list2.val){
            head = list1;
            list1 = list1.next;
        }
        else{
            head = list2;
            list2 = list2.next;
        }
        ListNode cur = head;
        cur.next=null;
        while(list1!=null&&list2!=null){
            if(list1.val<list2.val){
                cur.next = list1;
                list1 = list1.next;
            }
            else{
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
            cur.next = null;
        }
        if(list1==null&&list2!=null){
            cur.next =list2;
        }else if(list2==null&&list1!=null){
            cur.next = list1;
        }
        return head;
    }
}

18、树的子结构

题目描述
输入两颗二叉树A,B,判断B是不是A的子结构。

思路:首先遍历A树,找到A的根节点和B的根节点相同的点,找到之后再遍历A和B的各个子节点是否相同

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
  
    public TreeNode(int val) {
        this.val = val;
  
    }
  
}*/
public class Solution {
   public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root2==null) return false;
        if(root1==null && root2!=null) return false;       
        boolean flag = false;
        if(root1.val==root2.val){
            flag = isSubTree(root1,root2);
        }
        if(!flag){
            flag = HasSubtree(root1.left, root2);
        }
        if(!flag){
            flag = HasSubtree(root1.right, root2);
        }
        return flag;
    }
      
    private boolean isSubTree(TreeNode root1, TreeNode root2) {
        if(root2==null) return true;
        if(root1==null && root2!=null) return false;       
        if(root1.val==root2.val){
            return isSubTree(root1.left, root2.left) && isSubTree(root1.right, root2.right);
        }
        return false;
    }
}

19、二叉树的镜像

题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
二叉树的镜像定义:源二叉树
8
/
6 10
/ \ /
5 7 9 11
镜像二叉树
8
/
10 6
/ \ /
11 9 7 5

思路1:用栈结构(改成队列结构也可以),将节点依次入栈,每个入栈的节点都镜像他的子节点

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/

import java.util.Stack;
 
public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null){
            return;
        }
        Stack<TreeNode> stack = new Stack<TreeNode>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode node = stack.pop();
            if(node.left != null||node.right != null){
                TreeNode temp = node.left;
                node.left = node.right;
                node.right = temp;
            }
            if(node.left!=null){
                stack.push(node.left);
            }
            if(node.right!=null){
                stack.push(node.right);
            }
        }
    }
}

思路2:前序遍历的递归

public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null)  return;
        if(root.left != null || root.right != null)
        {   
        //创建临时节点,交换左右节点       
            TreeNode tempNode = null;           
            tempNode = root.left;
            root.left = root.right;
            root.right = tempNode;
            Mirror(root.left);
            Mirror(root.right);
             
        }
    }
}

20、顺时针打印矩阵

题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
       ArrayList<Integer> list = new ArrayList<Integer>();
        int rows = matrix.length;
        int columns = matrix[0].length;
        
        if(matrix == null || columns <= 0 || rows <= 0){
            return null;
        }
        int start = 0;
        while(columns > start *2 && rows > start * 2){
            print1Circle(list,matrix,columns,rows,start);
            start++;
        }
 
        return list;
    }
     
    public void print1Circle(ArrayList<Integer> list, int[][] matrix,int columns, int rows, int start) {
        int endX = columns - 1 - start;
        int endY = rows - 1 - start;
        
        //从左往右打印一行
        for (int i = start; i <= endX; i++) {
            list.add(matrix[start][i]);
        }
        
        //从上往下打印一列,至少有两行
        if (start < endY){
            for (int i = start+1; i <= endY; i++) {
            	list.add(matrix[i][endX]);
        	}
        }
         
         //从右往左打印一行,至少有两行两列
        if (start < endY && start < endX){
            for (int i = endX - 1; i >= start; i--) {
            	list.add(matrix[endY][i]);
        	}
        }
        
         //从下往上打印一列,至少有三行两列
        if (start < endY -1 && start < endX){
            for (int i = endY - 1; i >= start + 1; i--) {
            	list.add(matrix[i][start]);
        	}
        }     
        
    }
}

21、包含min函数的栈

题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。

import java.util.Stack;
 
/*思路:用一个栈data保存数据,用另外一个辅助栈min保存依次入栈最小的数
比如,data中依次入栈,5,  4,  3, 8, 10, 11, 12, 1
       则min依次入栈,5,  4,  3,3,3, 3, 3, 1
每次入栈的时候,如果入栈的元素比min中的栈顶元素小或等于则入栈,否则不如栈。
*/ 
public class Solution {
    Stack data=new Stack();
    Stack min=new Stack();
    
    public void push(int node) {
        if(min.empty()){
            min.push(node);
        }else{
            int top=(int)min.peek();
            if(node<top){
               min.push(node);
            }else{
                min.push(top);
            }
        }
        data.push(node);
    }
     
    public void pop() {
        if(!(data.empty())){
            data.pop();
            min.pop();
        }
    }
     
    public int top() {
        return (int)data.peek();
    }
     
    public int min() {
       if(min.empty()){
           return 0;
       }
       return (int)min.peek();    
    }
}

22、栈的压入、弹出序列

题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

【思路】借用一个辅助的栈,push序列依次入栈,每次都判断,栈顶元素和pop序列是否相等,相等则弹出栈,不相等,则push序列继续入栈,最后判断栈是否为空
举例:
入栈1,2,3,4,5
出栈4,5,3,2,1
首先1入辅助栈,此时栈顶1≠4,继续入栈2
此时栈顶2≠4,继续入栈3
此时栈顶3≠4,继续入栈4
此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
此时栈顶3≠5,继续入栈5
此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
….
依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA.length == 0 || popA.length == 0)
            return false;
        Stack<Integer> s = new Stack<Integer>();
        //用于标识弹出序列的位置
        int popIndex = 0;
        for(int i = 0; i< pushA.length;i++){
            s.push(pushA[i]);
            //如果栈不为空,且栈顶元素等于弹出序列
            while(!s.empty() &&s.peek() == popA[popIndex]){
                //出栈
                s.pop();
                //弹出序列向后一位
                popIndex++;
            }
        }
        return s.empty();
    }
}

23、从上往下打印二叉树——层次遍历

题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:一个队列容器,每次打印节点的时候把此节点的左右子节点加入进去

import java.util.*;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if(root==null){
            return list;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode treeNode = queue.poll();
            if (treeNode.left != null) {
                queue.offer(treeNode.left);
            }
            if (treeNode.right != null) {
                queue.offer(treeNode.right);
            }
            list.add(treeNode.val);
        }
        return list;
    }
}

24、二叉搜索树的后序遍历序列

题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

public class Solution {
    public static boolean VerifySquenceOfBST(int[] sequence) {
        if(sequence.length ==0){
            return false;
        }
        return VerifySquenceOfBST1(sequence,0,sequence.length-1);
    }
   
    public static boolean VerifySquenceOfBST1(int[] sequence,int start,int end) {
        
        if(start > end)
            return true;
        int root=sequence[end];//后序遍历最后一个节点为根节点
       
       //在二叉搜索树中左子树节点小于根节点
        int i=0;
        for(;i<end;i++){
            if(sequence[i]>root){
                break;
            }
        }
       
        //在二叉搜索树中右子树节点大于根节点
        int j=i;
        for(;j<end;j++){
            if(sequence[j]<root)
                return false;
        }
        boolean left=true;
        boolean right=true;
        if(i>start){
            left=VerifySquenceOfBST1(sequence,start,i-1);
        }
        if(i<sequence.length-1)
            right=VerifySquenceOfBST1(sequence,i,end-1);
        return (left&&right);
 
    }
}

25、二叉树中和为某一值的路径

题目描述
输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

import java.util.ArrayList;
import java.util.Stack;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/

public class Solution {
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
        ArrayList<ArrayList<Integer>> pathList=new ArrayList<ArrayList<Integer>>();
        if(root==null)
            return pathList;
        Stack<Integer> stack=new Stack<Integer>();
        FindPath(root,target,stack,pathList );
        return pathList;
         
    }
    private void FindPath(TreeNode root, int target, Stack<Integer> path, ArrayList<ArrayList<Integer>> pathList) {
        if(root==null)
            return;
        //如果是叶子节点,判断值是否是目标值
        if(root.left==null&&root.right==null){
            if(root.val==target){
                ArrayList<Integer> list=new ArrayList<Integer>();
                for(int i:path){
                    list.add(new Integer(i));
                }
                list.add(new Integer(root.val));
                pathList.add(list);
            }
        }
        else{//不是叶子节点就遍历其子节点
            path.push(new Integer(root.val));
            //是按照前序遍历的方式查找路径,如果向上退出到父节点时,要回到target值,而不是target-root.val
            FindPath(root.left, target-root.val, path, pathList);
            FindPath(root.right, target-root.val, path,  pathList);
            path.pop();
        }
         
    }
}

26、复杂链表的复制

题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。

方法一:用hashMap映射原链表,牺牲O(N)空间换来时间

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.HashMap; 
public class Solution {
 
    public RandomListNode Clone(RandomListNode pHead)
 
    {
 
        if(pHead == null) return null;
 
        HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>();
 
        RandomListNode newHead = new RandomListNode(pHead.label);//复制链表的头结点
 
        RandomListNode pre = pHead, newPre = newHead; 
        map.put(pre, newPre);
 
        //第一步,hashMap保存,原链表节点映射复制链表节点
        while(pre.next != null){ 
            newPre.next = new RandomListNode(pre.next.label); 
            pre = pre.next; 
            newPre = newPre.next; 
            map.put(pre, newPre); 
        }
 
        //第二步:找到对应的random
        pre = pHead; 
        newPre = newHead;
 
        while(newPre != null){ 
            newPre.random = map.get(pre.random); 
            pre = pre.next; 
            newPre = newPre.next; 
        }
 
        return newHead; 
    } 
}

方法二:不借用辅助空间

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead){
        if(pHead==null)
            return null;
        RandomListNode pCur = pHead;
        //第一步:复制next 如原来是A->B->C 变成A->A'->B->B'->C->C'
        while(pCur!=null){
            RandomListNode node = new RandomListNode(pCur.label);
            node.next = pCur.next;
            pCur.next = node;
            pCur = node.next;
        }
        
        //第二步
        pCur = pHead;
        //复制random pCur是原来链表的结点 pCur.next是复制pCur的结点
        while(pCur!=null){
            if(pCur.random!=null)
                pCur.next.random = pCur.random.next;
            pCur = pCur.next.next;
        }
        
        //第三步
        RandomListNode head = pHead.next;//复制链表的头结点
        RandomListNode cur = head;//偶数位置为复制链表
        pCur = pHead;//奇数位置为原链表
        //拆分链表
        while(pCur!=null){
            pCur.next = pCur.next.next;
            if(cur.next!=null)//注意最后一个复制节点的时候就没有next的next
                cur.next = cur.next.next;
            cur = cur.next;
            pCur = pCur.next;
        }
        return head;       
    }
}

27、二叉搜索树与双向链表

题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

方法一:递归中序遍历

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
//直接用中序遍历
public class Solution {
    TreeNode head = null;
    TreeNode realHead = null;//双向链表的头结点
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null) return null;
        Convert(pRootOfTree.left);
        if (head == null) {
            head = pRootOfTree;
            realHead = pRootOfTree;
        } else {
            head.right = pRootOfTree;
            pRootOfTree.left = head;
            head = pRootOfTree;
        }
        Convert(pRootOfTree.right);
        return realHead;
    }
}

方法二:

/** 非递归 */
import java.util.Stack;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null) return pRootOfTree;
         
        TreeNode list = null;
        Stack<TreeNode> s = new Stack<>();
        while(pRootOfTree != null || !s.isEmpty()){
            if(pRootOfTree != null) {
                s.push(pRootOfTree);
                pRootOfTree = pRootOfTree.right;
            } else {
                pRootOfTree = s.pop();
                if(list == null)
                    list = pRootOfTree;
                else {
                    list.left = pRootOfTree;
                    pRootOfTree.right = list;
                    list = pRootOfTree;
                }
                pRootOfTree = pRootOfTree.left;
            }
        }
         
        return list;
    }
}

28、字符串的排列

题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 结果请按字母顺序输出。

扩展:求字符串的全组合

如:abc,全组合为:a,b,c,ab,ac,bc,abc

public final class PermutationCombinationHolder {

    /** 1、数组元素的全组合 */
  public  static void combination(char[] chars) {
        char[] subchars = new char[chars.length]; //存储子组合数据的数组
        //全组合问题就是所有元素(记为n)中选1个元素的组合, 加上选2个元素的组合...加上选n个元素的组合的和
        for (int i = 0; i < chars.length; ++i) {
            final int m = i + 1;
            combination(chars, chars.length, m, subchars, m);
        }
    }

    /**
     *  n个元素选m个元素的组合问题的实现. 原理如下:
     *  从后往前选取, 选定位置i后, 再在前i-1个里面选取m-1个.
     *  如: 1, 2, 3, 4, 5 中选取3个元素.
     *  1) 选取5后, 再在前4个里面选取2个, 而前4个里面选取2个又是一个子问题, 递归即可;
     *  2) 如果不包含5, 直接选定4, 那么再在前3个里面选取2个, 而前三个里面选取2个又是一个子问题, 递归即可;
     *  3) 如果也不包含4, 直接选取3, 那么再在前2个里面选取2个, 刚好只有两个.
     *  纵向看, 1与2与3刚好是一个for循环, 初值为5, 终值为m.
     *  横向看, 该问题为一个前i-1个中选m-1的递归.
     */
    public static void combination(char[] chars, int n, int m, char[] subchars, int subn) {
        if (m == 0) { //出口
            for (int i = 0; i < subn; ++i) {
                System.out.print(subchars[i]);
            }
            System.out.println();
        } else {
            for (int i = n; i >= m; --i) { // 从后往前依次选定一个
                subchars[m - 1] = chars[i - 1]; // 选定一个后
                combination(chars, i - 1, m - 1, subchars, subn); // 从前i-1个里面选取m-1个进行递归
            }
        }
    }
///
///


    /** 2、数组元素的全排列 */
   public static void permutation(char[] chars) {
        permutation(chars, 0, chars.length - 1);
    }

    /** 数组中从索引begin到索引end之间的子数组参与到全排列 */
   public static void permutation(char[] chars, int begin, int end) {
        if (begin == end) { //只剩最后一个字符时为出口
            for (int i = 0; i < chars.length; ++i) {
                System.out.print(chars[i]);
            }
            System.out.println();
        } else {
            for (int i = begin; i <= end; ++i) { //每个字符依次固定到数组或子数组的第一个
                if (canSwap(chars, begin, i)) { //去重
                    swap(chars, begin, i); //交换
                    permutation(chars, begin + 1, end); //递归求子数组的全排列
                    swap(chars, begin, i); //还原
                }
            }
        }
    }

    public static void swap(char[] chars, int from, int to) {
        char temp = chars[from];
        chars[from] = chars[to];
        chars[to] = temp;
    }

    //判断去重
  public static boolean canSwap(char[] chars, int begin, int end) {
        for (int i = begin; i < end; ++i) {
            if (chars[i] == chars[end]) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        final char[] chars = new char[] {'a', 'b', 'c'};
        permutation(chars);
        System.out.println("===================");
        combination(chars);
    }
}

方法二:DFS

 import java.util.*;
 
public class Solution {
    private char [] seqs;
    private Integer [] book;
    //用于结果去重
    private HashSet<String> result = new HashSet<String>();
    /**
     * 输入一个字符串,按字典序打印出该字符串中字符的所有排列。
     * 例如输入字符串abc,则打印出由字符a,b,c
     * 所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 结果请按字母顺序输出。
       输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。\
     * @param str
     * @return
     */
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> arrange = new ArrayList<String>();
        if(str == null || str.isEmpty()) return arrange;
        char[] strs = str.toCharArray();
        seqs = new char[strs.length];
        book = new Integer[strs.length];
        for (int i = 0; i < book.length; i++) {
            book[i] = 0;
        }
        dfs(strs, 0);
        arrange.addAll(result);
        Collections.sort(arrange);
        return arrange;
    }
 
    /**
     * 深度遍历法
     */
    private void dfs(char[] arrs, int step){
        //走完所有可能 记录排列
        if(arrs.length == step){
            String str = "";
            for (int i = 0; i < seqs.length; i++) {
                str += seqs[i];
            }
            result.add(str);
            return; //返回上一步
        }
        //遍历整个序列,尝试每一种可能
        for (int i = 0; i < arrs.length; i++) {
            //是否走过
            if(book[i] == 0){
                seqs[step] = arrs[i];
                book[i] = 1;
                //下一步
                dfs(arrs, step + 1);
                //走完最后一步 后退一步
                book[i] = 0;
            }
        }
    }
}

方法三:字典序算法

 import java.util.*;

//步骤如下:
//1.从这个序列中从右至左找第一个左邻小于右邻的字符,记录下标为index1 ,如果找不到,说明求解完成。
//2.从这个序列中从右至左找第一个大于str[index1]的字符,记录下标为index2
//3.交换index1和index2的字符,对index1+1后的所有字符进行升序排序,此时得到的即为str按字典序的下一个排列
//4. 重复1~3的步骤,直到全部找完 
 
public class Solution {
    public ArrayList<String> Permutation(String str) {
           ArrayList<String> res = new ArrayList<>();

            if (str != null && str.length() > 0) {
                char[] seq = str.toCharArray();
                Arrays.sort(seq); //排列
                res.add(String.valueOf(seq)); //先输出一个解

                int len = seq.length;
                while (true) {
                    int p = len - 1, q;
                    //从后向前找一个seq[p - 1] < seq[p]
                    while (p >= 1 && seq[p - 1] >= seq[p]) --p;
                    if (p == 0) break; //已经是“最小”的排列,退出
                    //从p向后找最后一个比seq[p]大的数
                    q = p; --p;
                    while (q < len && seq[q] > seq[p]) q++;
                    --q;
                    //交换这两个位置上的值
                    swap(seq, q, p);
                    //将p之后的序列倒序排列
                    reverse(seq, p + 1);
                    res.add(String.valueOf(seq));
                }
            }

            return res;
        }

        public static void reverse(char[] seq, int start) {
            int len;
            if(seq == null || (len = seq.length) <= start)
                return;
            for (int i = 0; i < ((len - start) >> 1); i++) {
                int p = start + i, q = len - 1 - i;
                if (p != q)
                    swap(seq, p, q);
            }
        }

        public static void swap(char[] cs, int i, int j) {
            char temp = cs[i];
            cs[i] = cs[j];
            cs[j] = temp;
        }
}

29、数组中出现次数超过一半的数字

题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路:O(n)的思想是,定义两个变量result 和count,每次循环时,如果array[i]的值等于result ,则count自增,如不等并且count>0,则count自减,count==0,重新对temp赋值为当前array[i],count赋值为1。
如存在大于一半的数,直接返回result 就是了,但测试数据中有不存在的情况,所以最后又来了一遍校验,检查当前result 值是否出现过一半以上。

public class Solution {
   public int MoreThanHalfNum_Solution(int [] array) {
       if(array==null || array.length <= 0){
           return 0;
       }
       
       int result = array[0];
       int count = 1;
       for (int i = 1; i < array.length; i++) {
           if (array[i] == result) {
               count++;
           } else if (count > 0 ) {
               count--;
           } else if(count == 0){
               result = array[i];
               count = 1;
           }
       }
       //验证
       count=0;
       for(int i=0;i<array.length;i++){
           if(array[i]==result)
           		count++;
       }
       return count > array.length/2 ? result : 0;
   }
}

30、最小的K个数

题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

思路1:
经典常用的算法,快速排序的精髓利用快速排序划分的思想,每一次划分就会有一个数字位于以数组从小到达排列的的最终位置index;

位于index左边的数字都小于index对应的值,右边都大于index指向的值;

所以,当index > k-1时,表示k个最小数字一定在index的左边,此时,只需要对index的左边进行划分即可;

当index < k - 1时,说明index及index左边数字还没能满足k个数字,需要继续对k右边进行划分;

//这种方式的优点是时间复杂度较小为O(n),缺点就是需要修改输入数组。
import java.util.ArrayList;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList aList = new ArrayList();
        if(input.length == 0 || k > input.length || k <= 0)
            return aList;
        int low = 0;
        int high = input.length-1;
        int index = Partition(input,k,low,high);
        while(index != k-1){
            if (index > k-1) {
                high = index-1;
                index = Partition(input,k,low,high);
            }else{
                low = index+1;
                index = Partition(input,k,low,high);
            }
        }
        for (int i = 0; i < k; i++)
            aList.add(input[i]);
        return aList;
    }
     
    //快速排序的分段,小于某个数的放在左边,大于某个数的移到右边
    public int Partition(int[] input,int k,int low,int high){
        int pivotkey = input[k-1];
        swap(input,k-1,low);
        while(low < high){
            while(low < high && input[high] >= pivotkey)
                high--;
            swap(input,low,high);
            while(low < high && input[low] <= pivotkey)
                low++;
            swap(input,low,high);
        }
        return low;
    }
 
 
    private void swap(int[] input, int low, int high) {
        int temp = input[high];
        input[high] = input[low];
        input[low] = temp;
    }
}

思路2

  • 可以先创建一个大小为k的数据容器来存储最小的k个数字,从输入的n个整数中一个一个读入放入该容器中,如果容器中的数字少于k个,按题目要求直接返回空;

  • 如果容器中已有k个数字,而数组中还有值未加入,此时就不能直接插入了,而需要替换容器中的值。按以下步骤进行插入:

  • 1、先找到容器中的最大值;

  • 2、将待查入值和最大值比较,如果待查入值大于容器中的最大值,则直接舍弃这个待查入值即可;如果待查入值小于容器中的最小值,则用这个待查入值替换掉容器中的最大值;

  • 3、重复上述步骤,容器中最后就是整个数组的最小k个数字。

-对于这个容器的实现,我们可以使用最大堆的数据结构,最大堆中,根节点的值大于它的子树中的任意节点值。Java中的TreeSet类实现了红黑树的功能,它底层是通过TreeMap实现的,TreeSet中的数据会按照插入数据自动升序排列(按自然顺序)。因此我们直接将数据依次放入到TreeSet中,数组就会自动排序。

public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> leastNum = new ArrayList<Integer>();
        if (input == null || input.length < 1 || k < 1 || k > input.length)
            return leastNum;
        TreeSet<Integer> kSet = new TreeSet<Integer>();
        for (int i = 0; i < input.length; i++) {
            if (kSet.size() < k) {
                kSet.add(input[i]);
            } else {
                if (input[i] < kSet.last()) {
                    kSet.remove(kSet.last());
                    kSet.add(input[i]);
                }
            }
        }
        Iterator<Integer> it = kSet.iterator();
        while (it.hasNext()) {
            leastNum.add(it.next());
        }
 
        return leastNum;
    }

31、连续子数组的最大和

题目描述:
输入一个整型数组,数组里有正数和负数,数组中一个或者多个连续的数字组成一个子数组,求所有子数组的最大值,要求时间复杂度为O(N)

思路1:

/*
算法时间复杂度O(n)
用total记录累计值,maxSum记录和最大
基于思想:对于一个数A,若是A的左边累计数非负,那么加上A能使得值不小于A,认为累计值对
          整体和是有贡献的。如果前几项累计值负数,则认为有害于总和,total记录当前值。
此时 若和大于maxSum 则用maxSum记录下来
*/
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array.length==0)
            return 0;
        else{
            int total=array[0],maxSum=array[0];
            for(int i=1;i<array.length;i++){
                if(total>=0)
                    total+=array[i];
                else
                    total=array[i];
                if(total>maxSum)
                    maxSum=total;
            }
            return maxSum;
        }
         
    }
}

思路2:动态规划(相当重点)

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        //动态规划,主要是找到状态转移方程
        //设f(j)是从s[0]到s[j]最大和
        //f(j) = max(s[j], f[j-1]+s[j])
        if(array.length == 0)
            return 0;
        int result = Integer_MinValue;
        int sum = 0;
        for(int i = 0; i < array.length; ++i)
        {
            sum = max(array[i], sum + array[i]);
            result = max(result, sum);
        }
        return result;
    }
}

32、整数中1出现的次数(从1到n整数中1出现的次数)

题目描述
例如n=13的整数中1出现的次数,1~13中包含1的数字有1、10、11、12、13因此共出现6次

思路:
一、1的数目

编程之美上给出的规律:
1、 如果第i位(自右至左,从1开始标号)上的数字为0,则第i位可能出现1的次数由更高位决定(若没有高位,视高位为0),等于更高位数字 * 当前位数的权重10^(i-1)。

2、如果第i位上的数字为1,则第i位上可能出现1的次数不仅受更高位影响,还受低位影响(若没有低位,视低位为0),等于更高位数字 * 当前位数的权重10^(i-1)+(低位数字+1)。

3、如果第i位上的数字大于1,则第i位上可能出现1的次数仅由更高位决定(若没有高位,视高位为0),等于(更高位数字+1) * 当前位数的权重10^(i-1)。

二、X的数目
这里的 X∈[1,9] ,因为 X=0 不符合下列规律,需要单独计算。

首先要知道以下的规律:
从 1 至 10,在它们的个位数中,任意的 X 都出现了 1 次。
从 1 至 100,在它们的十位数中,任意的 X 都出现了 10 次。
从 1 至 1000,在它们的百位数中,任意的 X 都出现了 100 次。

依此类推,从 1 至 10^ i ,在它们的左数第二位(右数第 i 位)中,任意的 X 都出现了 10^(i-1) 次。

这个规律很容易验证,这里不再多做说明。
接下来以 n=2593,X=5 为例来解释如何得到数学公式。从 1 至 2593 中,数字 5 总计出现了 813 次,其中有 259 次出现在个位,260 次出现在十位,294 次出现在百位,0 次出现在千位。

现在依次分析这些数据,
首先是个位。从 1 至 2590 中,包含了 259 个 10,因此任意的 X 都出现了 259 次。最后剩余的三个数 2591, 2592 和 2593,因为它们最大的个位数字 3 < X,因此不会包含任何 5。(*也可以这么看,3 < X,则个位上可能出现的X的次数仅由更高位决定,等于更高位数字(259)10^(1-1)=259)。

然后是十位。从 1 至 2500 中,包含了 25 个 100,因此任意的 X 都出现了 25×10=250 次。剩下的数字是从 2501 至 2593,它们最大的十位数字 9 > X,因此会包含全部 10 个 5。最后总计 250 + 10 = 260。(*也可以这么看,9>X,则十位上可能出现的X的次数仅由更高位决定,等于更高位数字(25+1)10^(2-1)=260)。

接下来是百位。从 1 至 2000 中,包含了 2 个 1000,因此任意的 X 都出现了 2×100=200 次。剩下的数字是从 2001 至 2593,它们最大的百位数字 5 == X,这时情况就略微复杂,它们的百位肯定是包含 5 的,但不会包含全部 100 个。如果把百位是 5 的数字列出来,是从 2500 至 2593,数字的个数与百位和十位数字相关,是 93+1 = 94。最后总计 200 + 94 = 294。(*也可以这么看,5==X,则百位上可能出现X的次数不仅受更高位影响,还受低位影响,等于更高位数字(2)10^(3-1)+(93+1)=294)。

最后是千位。现在已经没有更高位,因此直接看最大的千位数字2< X,所以不会包含任何 5。(*也可以这么看,2< X,则千位上可能出现的X的次数仅由更高位决定,等于更高位数字(0)10^(4-1)=0)。
到此为止,已经计算出全部数字 5 的出现次数。

总结一下以上的算法,可以看到,当计算右数第 i 位包含的 X 的个数时:

  • 1、取第 i 位左边的数字(高位),乘以 10 ^(i−1) ,得到基础值 a 。
  • 2、取第 i 位数字,计算修正值:
  • 1、如果大于 X,则结果为 a+ 10 ^(i−1) 。
  • 2、如果小于 X,则结果为 a 。
  • 3、如果等 X,则取第 i 位右边(低位)数字,设为 b ,最后结果为 a+b+1 。

相应的代码非常简单,效率也非常高,时间复杂度只有 O( log 10 n) 。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
    	//当x=1时
        return NumberOfXBetween1AndN_Solution1(n,1);       
    }
  
 /**
 * @param n
 * @param x [1,9]
 * @return (从1到n整数中x出现的次数)
 */
    public int NumberOfXBetween1AndN_Solution1(int n,int x) {
        if(n<0||x<1||x>9)
            return 0;
        int high,low,curr,tmp,i = 1;
        high = n;
        int total = 0;
        while(high!=0){
            high = n/(int)Math.pow(10, i);// 获取第i位的高位
            tmp = n%(int)Math.pow(10, i);
            curr = tmp/(int)Math.pow(10, i-1);// 获取第i位
            low = tmp%(int)Math.pow(10, i-1);// 获取第i位的低位
            if(curr==x){
                total+= high*(int)Math.pow(10, i-1)+low+1;
            }else if(curr<x){
                total+=high*(int)Math.pow(10, i-1);
            }else{
                total+=(high+1)*(int)Math.pow(10, i-1);
            }
            i++;
        }
        return total;       
    }
  
}

33、把数组排成最小的数

题目描述:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

package cn.zhuang.offer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class Main {
 
/* 解题思路:
 * 考虑到大数问题,先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。
 * 排序规则如下:
 * 若ab > ba 则 a > b,
 * 若ab < ba 则 a < b,
 * 若ab = ba 则 a = b;
 * 解释说明:
 * 比如 "3" < "31"但是 "331" > "313",所以要将二者拼接起来进行比较
 */
	public static String PrintMinNumber(int [] numbers) {
        if(numbers == null || numbers.length == 0) return "";
        int len = numbers.length;
        String[] str = new String[len];
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < len; i++){
            str[i] = String.valueOf(numbers[i]);
        }
        
        //此处较为重要,新的排序规则,如若取最大值,~c1.compareTo(c2)
        Arrays.sort(str,new Comparator<String>(){
            @Override
            public int compare(String s1, String s2) {
                String c1 = s1 + s2;
                String c2 = s2 + s1;
                return c1.compareTo(c2);
            }
        });
        for(int i = 0; i < len; i++){
            sb.append(str[i]);
        }
        return sb.toString();
    }
	
	public static void main(String[] args){
		int[] a = {3,32,421};
		System.out.println(PrintMinNumber(a));
	}

}

java中的compareto方法,返回参与比较的前后两个字符串的asc码的差值,看下面一组代码
String a=“a”,b=“b”;
System.out.println(a.compareto.b);
则输出-1;
若a=“a”,b="a"则输出0;
若a=“b”,b="a"则输出1;

单个字符这样比较,若字符串比较长呢??
若a=“ab”,b=“b”,则输出-1;
若a=“abcdef”,b="b"则输出-1;
也就是说,如果两个字符串首字母不同,则该方法返回首字母的asc码的差值

如果首字母相同呢??
若a=“ab”,b=“a”,输出1;
若a=“abcdef”,b="a"输出5;
若a=“abcdef”,b="abc"输出3;
若a=“abcdef”,b="ace"输出-1;
即参与比较的两个字符串如果首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的asc码差值,如果两个字符串不一样长,可以参与比较的字符又完全一样,则返回两个字符串的长度差值

34、丑数

题目描述:
把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路1:最简单的方法就是先通过将一个数不断除以2,3,5来判定该数是不是丑数,而后在从1开始,依次往后判断每个数是不是丑数,并记下丑数的个数,这样当计算的个数为给定值时,便是需要求的第n个丑数,这种方法的时间复杂度为O(k),这里的k为第n个丑数的大小,比如第1500个丑数的大小为859963392,那么就需要判断859963392次,时间效率非常低。

public boolean IsUgly(int number)
{
    while(number % 2 == 0)
        number /= 2;
    while(number % 3 == 0)
        number /= 3;
    while(number % 5 == 0)
        number /= 5;
 
    return (number == 1) ? true : false;
}

public int GetUglyNumber(int index)
{
    if(index <= 0)
        return 0;
 
    int number = 0;
    int uglyFound = 0;
    while(uglyFound < index)
    {
        ++number;
 
        if(IsUgly(number))
        {
            ++uglyFound;
        }
    }
 
    return number;
}

思路Better2:直观的优化措施就是看能不能将时间复杂度降低到O(n),即只在丑数上花时间,而不在非丑数上浪费时间。剑指offer上给的思路很好,用O(n)的辅助空间来得到O(n)的时间复杂度。其核心思想是:每一个丑数必然是由之前的某个丑数与2,3或5的乘积得到的,这样下一个丑数就用之前的丑数分别乘以2,3,5,找出这三这种最小的并且大于当前最大丑数的值,即为下一个要求的丑数。

import java.util.*;
public class Solution
{
    public int GetUglyNumber_Solution(int n)
    {
        if(n==0)return 0;
        ArrayList<Integer> res=new ArrayList<Integer>();
        res.add(1);
        int i2=0,i3=0,i5=0;
        while(res.size()<n)
        {
            int m2=res.get(i2)*2;
            int m3=res.get(i3)*3;
            int m5=res.get(i5)*5;
            int min=Math.min(m2,Math.min(m3,m5));
            res.add(min);
            if(min==m2)i2++;
            if(min==m3)i3++;
            if(min==m5)i5++;
        }
        return res.get(res.size()-1);
    }
}

35、第一个只出现一次的字符

描述:在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符的位置。若为空串,返回-1。位置索引从0开始,如“abaccdrff”,则输出“b”

第一种,数组方法:

public class Solution {
   public int FirstNotRepeatingChar(String str) {
        if (str.length() == 0) {
            return  -1;
        }
        char c = 'A';
        if(str.charAt(0) >= 'a'){
            c = 'a';
        }
        int[] counts = new int[26];
        for (int i = 0; i < str.length(); i++) {
            counts[str.charAt(i) - c]++;
        }
        for (int i = 0; i < str.length(); i++) {
            if (counts[str.charAt(i) - c] == 1){
                return i;
            }
        }
        return -1;
    }
}

第二种,HashMap方法:

import java.util.LinkedHashMap;
// use linkedhashmap to keep the order
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        LinkedHashMap <Character, Integer> map = new LinkedHashMap<Character, Integer>();
        for(int i=0;i<str.length();i++){
            if(map.containsKey(str.charAt(i))){
                int time = map.get(str.charAt(i));
                map.put(str.charAt(i), ++time);
            }
            else {
                map.put(str.charAt(i), 1);
            }
        }
        int pos = -1;  
        int i=0;
        for(;i<str.length();i++){
            char c = str.charAt(i);
            if (map.get(c) == 1) {
                return i;
            }
        }
        return pos;
    }
}

36、数组中的逆序对

题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
{7,5,6,4},则76,75,74,64,65,一共5个

思路:
这里写图片描述

public class Solution {
     private  int reversePair = 0;
    public int InversePairs(int [] array) {
        if(array==null)
            return 0;
        int len = array.length;
        if(len==0)
            return 0;
        sort(array,0,len-1);
        return reversePair;
    }
    
    private void sort(int[]arr,int start,int end){
        if(start<end){
            int mid = start+(end-start)/2;
            sort(arr,start,mid);
            sort(arr,mid+1,end);
            merger(arr,start,mid,mid+1,end);
        }
    }
 
    private void merger(int[] arr, int start1, int end1, int start2, int end2) {
        int len= end2-start1+1;
        int[] anx = new int[len];
        int k = end2-start1+1;
         
        int i = end1;
        int j= end2;
        while(i>=start1&j>=start2){
            if(arr[i]>arr[j]){
                anx[--k]=arr[i];
                i--;
                reversePair = reversePair+(j-start2+1);
            }
            else{
                anx[--k]=arr[j];
                j--;
            }
        }
        for(;i>=start1;i--)
            anx[--k]=arr[i];
        for(;j>=start2;j--)
            anx[--k]=arr[j];
         
        for(int m=0;m<len;m++)
            arr[start1++]=anx[m];
         
    }
}


37、两个链表的第一个公共交点

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       if (pHead1 == null||pHead2 == null) {
            return null;
        }
        int count1 = 0;
        ListNode p1 = pHead1;
        while (p1!=null){
            p1 = p1.next;
            count1++;
        }
        int count2 = 0;
        ListNode p2 = pHead2;
        while (p2!=null){
            p2 = p2.next;
            count2++;
        }
        int flag = count1 - count2;
        if (flag > 0){
            while (flag>0){
                pHead1 = pHead1.next;
                flag --;
            }
        while (pHead1!=pHead2){
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        return pHead1;
    }
        if (flag <= 0){
            while (flag<0){
                pHead2 = pHead2.next;
                flag ++;
            }
            while (pHead1 != pHead2){
                pHead2 = pHead2.next;
                pHead1 = pHead1.next;
            }
            return pHead1;
        }
        return null;
    }
}

38、数字在排序数组中出现的次数

思路:重点是有序数组这个条件,采用二分查找法,分别找到第一个和最后一个,这样无论最好最坏复杂度都是O(lgN)

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
      int num = 0;
        if (array != null && array.length > 0) {
            int firstKIndex = getFirstK(array, k, 0, array.length - 1);
            int lastKIndex = getLastK(array, k, 0, array.length - 1);
            if (firstKIndex > -1 && lastKIndex > -1)
                num = lastKIndex - firstKIndex + 1;
        }
        return num;
    }
    
    /*
    * 找到第一个出现的数字的下标
    */
    public  int getFirstK(int[] array, int k, int start, int end) {
        if (start > end)
            return -1;
        int middleIndex = start + (end - start) / 2;
        int middleData = array[middleIndex];
        if (middleData == k) {
        //判断是不是第一个K,前一个不等于K,就是第一个K
            if (middleIndex > 0 && array[middleIndex - 1] != k || middleIndex == 0) {
                return middleIndex;
            } else
                end = middleIndex - 1;
        } else if (middleData > k) {
            end = middleIndex - 1;
        } else
            start = middleIndex + 1;
        return getFirstK(array, k, start, end);
    }
    
     /*
    * 找到最后一个出现的数字的下标
    */
    public  int getLastK(int array[], int k, int start, int end) {
        if (start > end) {
            return -1;
        }
        int middleIndex = (start + end) / 2;
        int middleData = array[middleIndex];
        if (middleData == k) {
         //判断是不是最后一个K,后一个不等于K,就是最后一个K
            if (middleIndex < array.length - 1 && array[middleIndex + 1] != k || middleIndex == array.length - 1)
                return middleIndex;
            else
                start = middleIndex + 1;
        } else if (middleData < k) {
            start = middleIndex + 1;
        } else
            end = middleIndex - 1;
        return getLastK(array, k, start, end);
 
    }
}

39、二叉树的深度

题目描述:
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

思路:递归,下一层根节点到上一层根节点,深度加1,判断左子树和右子树的最大值,然后+1

/*
public class TreeNode {
	int val = 0;
	TreeNode left = null;
	TreeNode right = null;
	public TreeNode(int val) {
        this.val = val;
    }
};*/
public class Solution {
     public int getHeight(TreeNode root) {
        if (root == null)
            return 0;
        return max(getHeight(root.left), getHeight(root.right)) + 1;
    }
 
    private int max(int a, int b) {
        return (a > b) ? a : b;
    }
}

###扩展题:判断二叉树平衡

描述:如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

第一种思路:简洁,但是效率不高,因为会重复遍历子节点

public class Solution {
   public boolean IsBalanced(TreeNode root) {
        if (root == null)
            return true;
 
        if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1)
            return false;
 
         
        return IsBalanced(root.left) && IsBalanced(root.right);
 
    }
 
    public int getHeight(TreeNode root) {
        if (root == null)
            return 0;
        return max(getHeight(root.left), getHeight(root.right)) + 1;
    }
 
    private int max(int a, int b) {
        return (a > b) ? a : b;
    }
}

第二种Better思路:从底向上判断,这样可以记录下一层的深度

public class Solution {
  public boolean IsBalanced(TreeNode root) {
		int depth = 0;
		return IsBalanced(root, depth);
	}

	public boolean IsBalanced(TreeNode root, int depth) {
		if (root == null) {
			depth = 0;
			return true;
		}

		int left = 0, right = 0;
		if (IsBalanced(root.left, left) && IsBalanced(root.right, right)) {
			int diff = left - right;
			if (diff <= 1 && diff >= -1) {
				depth = 1 + (left > right ? left : right);
				return true;
			}
		}

		return false;
	}
}

40-数组只指出现一次的数字:

异或去重:

适应情景:数组中只有一个数字出现了奇数次,其他的都出现了偶数次。或者只有一个数字出现了偶数次,其他的都出现了奇数次
性质:对于任意的a:aa=0,a0=a,a^(-1)=~a。

下面来看三道题目:

1、1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?

当然,这道题,可以用最直观的方法来做,将所有的数加起来,减去1+2+3+…+1000的和,得到的即是重复的那个数,该方法很容易理解,而且效率很高,也不需要辅助空间,唯一的不足时,如果范围不是1000,而是更大的数字,可能会发生溢出。

我们考虑用异或操作来解决该问题。现在问题是要求重复的那个数字,我们姑且假设该数字式n吧,如果我们能想办法把1-1000中除n以外的数字全部异或两次,而数字n只异或一次,就可以把1-1000中出n以外的所有数字消去,这样就只剩下n了。我们首先把所有的数字异或,记为T,可以得到如下:

T = 1234…n…n…1000 = 123…^1000(结果中不含n)

而后我们再让T与1-1000之间的所有数字(仅包含一个n)异或,便可得到该重复数字n。如下所示:

T(a234…n…1000) = T(Tn) = 0^n = n

2、一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

样例输入:2 4 3 6 3 2 5 5
样例输出:4 6

思路:异或去重是知道如果只有一个只出现一次的数字的求法,但这里是有两个只出现一次的数字,我们便要想办法把他分为两个子数组,每个子数组中包含一个只出现一次的数字,其他的数字都出现了两次。

首先依然从头到尾异或所有的数字,如ABCCDDEEFF,这样得到的结果就是AB异或的结果,那么在异或后的结果中找出其二进制中最右边为1的位,该位既然为1,说明AB对应的该位肯定不同,必定一个为1,一个为0,因此我们可以考虑根据此位是否为1来划分这两个子数组,这样两个只出现一次的数字就分开了

但我们还要保证出现两次的数字都分到同一个子数组中,很明显,相同的数字相同的位上的值是相同的,要么都为1,要么都为0,因此我们同样可以通过判断该位是否为1来将这些出现两次的数字划分到同一个子数组中,该位如果为1,就分到一个子数组中,如果为0,就分到另一个子数组中。

这样就能保证每个子数组中只有一个出现一次的数字,其他的数字都出现两次,分别全部异或即可得到这两个只出现一次的数字。时间复杂度为O(n)。

另外,**所有元素异或后,在找出最右边为1的时,**X&(-X)之后得到的数字,是把X中最右边的1保留下来

注意,这里的-X是X的相反数,-X是对X所有位取反+1

package cn.zhuang.offer;

public class Main {
	
	public static void FindNumsAppearOnce(int[] arr)  
	{  
	    int len = arr.length;
	    if(len<2)  
	        return;  
	  
	    int i;  
	    int AllXOR = 0;  
	    //全部异或  
	    for(i=0;i<len;i++)  
	        AllXOR ^= arr[i];  
	  
	  //找出第几位为1,如010
	    int res = FindFirstBit1(AllXOR);  
	  
	    int num1=0, num2 = 0;  
	    for(i=0;i<len;i++)  
	    {  //分成了两组
	        if(IsBit1(arr[i],res))  
	            num1 ^= arr[i];  
	        else  
	            num2 ^= arr[i];  
	    }  
	    System.out.println(num1+"and"+num2);
	}
	   
	/* 
	返回num的最低位的1,其他各位都为0 
	*/  
	public static int FindFirstBit1(int num)  
	{  
	    //二者与后得到的数,将num最右边的1保留下来,其他位的全部置为了0  
	    return num & (-num);  
	}  
	  
	/* 
	判断data中特定的位是否为1, 
	这里的要判断的特定的位由res确定, 
	res中只有一位为1,其他位均为0,由FindFirstBit1函数返回, 
	而data中要判断的位便是res中这唯一的1所在的位 
	*/  
	public static boolean IsBit1(int data,int res)  
	{  
	    return ((data & res)==0) ? false:true;  
	}  

	public static void main(String[] args) {
		int[] a = { 1,1,2,2,3,44,55,55,66,66,34,34,5,5,7,7 };
		FindNumsAppearOnce(a);
	}

}


3、题目:一个int数组中有三个数字a、b、c只出现一次,其他数字都出现了两次。请找出三个只出现一次的数字。

通用性的方法,对于2个,3个出现一次的数字这类的问题,都可以按照该思路去求解,只是时间复杂度可能要稍微大些,为O(8* sizeof(int)* N),8位* 4字节* N自然就是数组的长度了。

该方法的思路如下:

首先由于有3个数字出现一次,其他的都出现两次,所以N肯定为奇数该方法通过扫描整数的每一位来逐个判断。

再看这3个只出现一次的数字,他们的bit位肯定不可能全部相同,至少有一个位,要么110,要么001,我们可以通过扫面int的所有bit位,扫描每个bit位的时候,遍历数组,首先找到一个,另外的两个就可以按照上题的解法。

如何找到第一个数?

我们遍历数组,分别统计该数组元素中该bit位为1和0的元素个数,分别设为count1和count0,并同时将所有该bit位为1的元素异或结果为temp1,所有该bit位为0的元素异或,得到的结果为temp0。

如果111或000这两种状态,肯定三个都在count为奇数的里边,不好区分,主要找110或001的区分

先看第一种情况,001,如果count1为奇数,则为111或001,则在count1的数组里,如果temp00,则为111,此位不是判断的位,下一位,如果temp01,则为001,temp1则为所求。

先看第二种情况,110,如果count1为偶数,则为110或000,则在count1的数组里,如果temp10,则为000,此位不是判断的位,下一位,如果temp11,则为110,temp0则为所求。

说白了,就是看count1为奇数时,temp0是否为1,count1为偶数时,temp1是否为1

package cn.zhuang.offer;

public class Main {
	
	/*
	 * 找出这三个只出现一次的数字
	 */
	public static void FindThreeNumsAppearOnce(int[] arr, int len) {
		if (len < 3)
			return;

		int num1 = FindOneNumAppearOnce(arr, len);
		System.out.println(num1);

		// 找到第一个找出的数字,并与最后一个元素交换,便于接下来剩下的两个数字
		int i;
		for (i = 0; i < len; i++){
			if (num1 == arr[i]){
				int temp ;
				temp = arr[i];
				arr[i] = arr[len -1];
				arr[len - 1]=temp;
				break;
			}
		}
		
		FindTwoNumsAppearOnce(arr, len - 1);
	}

	/*
	 * 通过扫面每一位,先找出一个只出现一次的数
	 */
	public static int FindOneNumAppearOnce(int[] arr, int len) {
		int count1 = 0; // 某一位上1的个数
		int count0 = 0; // 某一位上0的个数
		int temp1 = 0; // 某一位为1的所有数相异或的结果
		int temp0 = 0; // 某一位为0的所有数相异或的结果

		int i, j;
		// 循环计算每一位的以上四个数据
		for (i = 0; i < 32; i++) {
			count1 = count0 = temp1 = temp0 = 0;// 每次计算下一位时清零
			for (j = 0; j < len; j++) {
				// 每次向左移一位进行计算
				if ((arr[j] & (1 << i)) != 0) {
					temp1 ^= arr[j];
					count1++;
				} else {
					temp0 ^= arr[j];
					count0++;
				}
			}

			if ((temp1 & 1) != 0) {
				// 某位上有奇数个1
				if (temp0 == 0) // 此时3个不同数的该位都为1
					continue;
				else
					// 此时3个不同数的该位有1个1,2个0
					return temp1;
			} else {
				// 某位上有偶数个1
				if (temp1 == 0) // 此时3个不同数的该位都为0
					continue;
				else
					// 此时3个不同数的该位有1个0,2个1
					return temp0;
			}
		}

		return Integer.MAX_VALUE;
	}
	
	public static void FindTwoNumsAppearOnce(int[] arr, int len) {

		int i;
		int AllXOR = 0;
		// 全部异或
		for (i = 0; i < len; i++)
			AllXOR ^= arr[i];

		int res = FindFirstBit1(AllXOR);

		int num1 = 0;
		int num2 = 0;

		for (i = 0; i < len; i++) {
			if (IsBit1(arr[i], res))
				num1 ^= arr[i];
			else
				num2 ^= arr[i];
		}
		
		System.out.println(num1);
		System.out.println(num2);
	}

	/*
	 * 返回num的最低位的1,其他各位都为0
	 */
	public static int FindFirstBit1(int num) {
		// 二者与后得到的数,将num最右边的1保留下来,其他位的全部置为了0
		return num & (-num);
	}

	/*
	 * 判断data中特定的位是否为1, 这里的要判断的特定的位由res确定, res中只有一位为1,其他位均为0,由FindFirstBit1函数返回,
	 * 而data中要判断的位便是res中这唯一的1所在的位
	 */
	public static boolean IsBit1(int data, int res) {
		return ((data & res) == 0) ? false : true;
	}

	public static void main(String[] args) {
		int[] a = { 1,2,2,3,44,55,55,66,66,34,34,5,5,7,7 };
		FindThreeNumsAppearOnce(a, a.length);
	}

}

41、和为S的两个数字

题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,任意输出一对。
输入数组{1,2,4,7,11,15}和15,输出4,11

import java.util.ArrayList;
public class Solution {
    /*
    * i,j分别表示数组两端下表
    * 当array[i]+array[j]>S时,j-- 尾端向前移动,两数据和增大
    * 当array[i]+array[j]=S时,将array[i],array[j]依次添加到ArrayList中
    * 当array[i]+array[j]<S时,i++前段向后移动,两数据和减小
    */
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if (array == null || array.length < 2) {
            return list;
        }
        int i=0,j=array.length-1;
        while(i<j){
            if(array[i]+array[j]==sum){
            list.add(array[i]);
            list.add(array[j]);
                return list;
           }else if(array[i]+array[j]>sum){
                j--;
            }else{
                i++;
            }
             
        }
        return list;
    }
}

###扩展题:和为S的连续正数序列

描述:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

import java.util.ArrayList;

public class Solution {
	public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
		ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
		if (sum < 3)
			return list;
		ArrayList<Integer> l = new ArrayList<Integer>();
		int small = 2;
		int middle = (1 + sum) / 2;//因为至少连续2个且顺序增加,所以取中间值
		l.add(1);
		l.add(2);
		int s = 3;
		if (s == sum) {
			list.add(new ArrayList<Integer>(l));
		}

		while (small <= middle) {
			small++;
			s += small;
			l.add(small);
			if (s == sum) {
				list.add(new ArrayList<Integer>(l));
			}
      //两个指针,若大,减去左边数字,若小,加右边数字
			while (s > sum && small <= middle) {
				s -= l.remove(0);
				if (s == sum) {
					list.add(new ArrayList<Integer>(l));
				}
			}

		}
		return list;
	}
}

42、反转单词

描述:反转英文单词,例如,“student. a am I”反转成“I am a student.”

思想:就是先翻转所有字符,再逐个单词翻转

public class Solution {
     public String ReverseSentence(String str) {
         if(str==null||str.trim().equals(""))// trim掉多余空格
             return str;
         String[] words = str.split(" ");// 以空格切分出各个单词
         StringBuffer buffer = new StringBuffer();
         for(int i=0;i<words.length;i++){

             buffer.append(reverse1(words[i].toCharArray(), 0, words[i].length()-1)).append(" ");
         }
         if(buffer.length()>0)
             buffer.deleteCharAt(buffer.length()-1);// 删除最后一个空格
         return reverse1(buffer.toString().toCharArray(), 0, buffer.length()-1);
      
 }
 	private String reverse1(char[] str, int l, int r) {
         // TODO Auto-generated method stub
         if(l>r)
             return "";
         char tmp;
         while(l<r){
             tmp = str[l];
             str[l] = str[r];
             str[r] = tmp;
             l++;
             r--;
         }
         return String.valueOf(str);
     }
}

扩展题:字符串左移n位,abcde,左移2位,cdeab

思路:前n位反转,后几位反转,最后总的反转

public class Solution {
    public String LeftRotateString(String str,int n) {
        char[] chars = str.toCharArray();        
        if(chars.length < n) return "";
        reverse(chars, 0, n-1);
        reverse(chars, n, chars.length-1);
        reverse(chars, 0, chars.length-1);
        StringBuilder sb = new StringBuilder(chars.length);
        for(char c:chars){
            sb.append(c);
        }
        return sb.toString();
    }
     
    public void reverse(char[] chars,int low,int high){
        char temp;
        while(low<high){
            temp = chars[low];
            chars[low] = chars[high];
            chars[high] = temp;
            low++;
            high--;
        }
    }
}

44、扑克牌的顺序

从扑克牌中抽取5张牌,判断是否连续,大小王是任意数字

思路:选取5张牌,首先去0,然后进行排序,最大值减最小值是否小于等于4,大于4,为false,
然后相邻相减应该大于0小于5,否的为false

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers == null || numbers.length == 0 || numbers.length > 5){
            return false;
        }
        ArrayList<Integer> al = new ArrayList<>();
        int len = numbers.length;
        int count = 0;
        for(int i = 0; i < len; i++){
        //必须去0,因为0可以是任意数字,如0,2,3,5,6,也是连续的
            if(0 == numbers[i]){
                count++;
            }else{
                al.add(numbers[i]);
            }      
        }
         //非0的排序
        Collections.sort(al);
        int len1 = al.size();
        //大于4,肯定false
        if(Math.abs(al.get(0) - al.get(len1 - 1)) > 4){
            return false;
        }
        for(int i = 0; i < len1 - 1; i++){
         //相邻的只差,大于0不能重复,大于4肯定false
            int temp = al.get(i + 1) - al.get(i);
            if(0 < temp && temp < 5){
                continue;
            }else{
                return false;
            }
        }
        return true;
    }
}

45、约瑟夫环问题:圆圈中最后一个数字

题目描述:一个环,每次删除第m个数字,求最后一个数字,如0,1,2,3,4这5个数字,从0开始每次删除第3个数字,则依次删除2,0,4,1,最后一个数字是3

第一种解法:数组O(m*N)

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<1||m<1) return -1;
        int[] array = new int[n];
        int i = -1,step = 0, count = n;
        while(count>0){   //跳出循环时将最后一个元素也设置为了-1
            i++;          //指向上一个被删除对象的下一个元素。
            if(i>=n) i=0;  //模拟环。
            if(array[i] == -1) continue; //跳过被删除的对象。
            step++;                     //记录已走过的。
            if(step==m) {               //找到待删除的对象。
                array[i]=-1;
                step = 0;
                count--;
            }        
        }
        return i;//返回跳出循环时的i,即最后一个被设置为-1的元素
    }
}

第二种:链表,O(N)

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if (m == 0 || n == 0) {
            return -1;
        }
        ArrayList<Integer> data = new ArrayList<Integer>();
        for (int i = 0; i < n; i++) {
            data.add(i);
        }
        int index = -1;
        while (data.size() > 1) {
        //重点是此步
            index = (index + m) % data.size();
            data.remove(index);
            index--;
        }
        return data.get(0);
    }

}

第三种better解法:约瑟夫经典解法,O(N),空间复杂度O(1)

public class Solution {
    public int LastRemaining_Solution(int n,int m) {
        if(n==0) return -1;
         
       int s=0;
       for(int i=2;i<=n;i++){
           s=(s+m)%i;
       }
       return s;
    }
}

46、求1+2+3+…+n

题目描述:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

public class Solution {
 
    public int Sum_Solution(int n) {
        int result = 0;
        int a = 1;
        boolean value = ((n!=0) && a == (result = Sum_Solution(n-1)));
        result += n;    
        return result;
    }
 
}

47、不用加减乘除做加法

题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

**思路:**首先看十进制是如何做的: 5+7=12,三步走

第一步:相加各位的值,不算进位,得到2。

第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。

第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。

同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。

第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。

第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。

public class Solution {
    public int Add(int num1,int num2) {
        while (num2!=0) {
            int temp = num1^num2;//不算进位,各位相加
            num2 = (num1&num2)<<1;//得到进位数
            num1 = temp;//与进位数相加,即循环以上操作
        }
        return num1;
    }
}

49、把字符串转换成整数

此题主要是注意细节:
1、功能测试:输入有±号情况,区分正负数和0
2、特殊输入:空字符串情况,输入非数字字符串情况,如a12
3、边界值:最大正整数和最小负整数溢出情况

public class Solution {
    public int StrToInt(String str)
    {
        if (str.equals("") || str.length() == 0)//空字符串情况
            return 0;
        char[] a = str.toCharArray();
        int i = 0;
        boolean fuhao = true;//+-符号位
        if (a[0] == '-'){
            fuhao = false;
            i = 1;//第一位如果是-号,则从第二位开始循环
        }           
        int sum = 0;
        for (; i < a.length; i++)
        {
            if (a[i] == '+')
                continue;
            if (a[i] < 48 || a[i] > 57)
                return 0;//有非数字字符
            sum = sum * 10 + a[i] - 48;
            
            //判断是否溢出,正整数最大0X7FFF FFFF,最小负整数0X8000 0000
            if((fuhao && sum > 0X7fffffff) || (!fuhao && sum < 0X80000000)){
                sum = 0;
                break;
            }
            
        }
        return fuhao ? sum : sum * -1;
    }
}

51、数组中重复的数

题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

思路1:排序,时间复杂度O(NlogN)

思路2:Hash表,时间和空间复杂度都是O(N)

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
public class Solution {

    boolean duplicate(int numbers[],int length,int [] duplication) {
        HashMap<Integer, Integer> countMap = new HashMap<Integer, Integer>();
        if(length < 2||numbers==null){
            return false;
        }
        int j = 1;
        for(int i = 0;i < length;i++){
            if(countMap.get(numbers[i]) == null){
                j = 1;
                countMap.put(numbers[i], j);
            }else{
                j = countMap.get(numbers[i]);
                j++;
                countMap.put(numbers[i], j);
            }
        }
        Iterator iter = countMap.entrySet().iterator();
        while(iter.hasNext()){
            Entry<Integer, Integer> entry = (Entry<Integer, Integer>) iter.next();
            Integer key = entry.getKey();
            Integer val = countMap.get(key);
            if(val > 1){
                duplication[0] = key;
                return true;
            }
        }
        return false;
    }
}

思路3:用Set集合,因为Set集合不允许有重复的,时间和空间复杂度都是O(N)

import java.util.Set;
import java.util.HashSet;
public class Solution {
	boolean duplicate(int numbers[], int length, int[] duplication) {
        if(length < 2||numbers==null){
            return false;
        }
        
		Set<Integer> ss = new HashSet<Integer>();
		for (int i = 0; i < numbers.length; i++) {
			if (ss.contains(numbers[i])) {
				duplication[0] = numbers[i];
				return true;
			} else {
				ss.add(numbers[i]);
			}
		}
		return false;
	}
}

思路4better:时间复杂度O(N),所有操作都是在输入数组上进行,所以不需要分配额外空间,空间复杂度为O(1)

这里写图片描述

public class Solution {

    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null||length<0)return false;
         
        for(int i = 0;i < length; i++){
            if(numbers[i]<0||numbers[i]>length-1)
                return false;
        }
         
        for(int i = 0;i< length;i++){
            while(numbers[i]!=i){
                if(numbers[i]==numbers[numbers[i]]){
                    duplication[0] = numbers[i];
                    return true;
                }
                else{
                    //没有找到,然后则交换,使该数到正确的位置去
                    swap(numbers,i,numbers[i]);
                }
            }
            
        }
         return false;
    }
     
    //交换二数
    private void swap(int[]a, int i, int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

52、构建乘积数组

题目描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0] * A[1] * … * A[i-1]* A[i+1]* …*A[n-1]。不能使用除法。

思路:1.计算前i - 1个元素的乘积,及后N - i个元素的乘积分别保存在两个数组中

这里写图片描述

import java.util.ArrayList;
public class Solution {
   public int[] multiply(int[] A) {
        int len = A.length;
        int forword[] = new int[len];
        int backword[] = new int[len];
        int B[] = new int[len];
        forword[0] = 1;
        backword[0] = 1;
        for(int i = 1;i < len; i++){
            forword[i] = A[i - 1]*forword[i-1];
            backword[i] = A[len - i]*backword[i - 1];
        }
        for(int i = 0; i < len; i++){
            B[i] = forword[i] * backword[len - i -1];
        }
        return B;
    }
}

53、正则表达式匹配

题目描述
请实现一个函数用来匹配包括’.‘和’* ‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’* ’ 表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab* ac* a"匹配,但是与"aa.a"和"ab*a"均不匹配

public class Solution {
    public boolean match(char[] str, char[] pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        int strIndex = 0;
        int patternIndex = 0;
        return matchCore(str, strIndex, pattern, patternIndex);
}
 
    public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {
        //str到尾,pattern到尾,匹配成功
        if (strIndex == str.length && patternIndex == pattern.length) {
            return true;
        }
        //str未到尾,pattern到尾,匹配失败
        if (strIndex != str.length && patternIndex == pattern.length) {
            return false;
        }
        //str到尾,pattern未到尾(不一定匹配失败,因为a*可以匹配0个字符)
        if (strIndex == str.length && patternIndex != pattern.length) {
            //只有pattern剩下的部分类似a*b*c*的形式,才匹配成功
            if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
                return matchCore(str, strIndex, pattern, patternIndex + 2);
            }
            return false;
        }

        //str未到尾,pattern未到尾
        if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
            if (pattern[patternIndex] == str[strIndex] || (pattern[patternIndex] == '.' && strIndex != str.length)) {
            //三种可能:
                //1、模式串当前字符出现0次,即*表示当前字符出现0次,则str=str,pattern=pattern+2;
                //2、模式串当前字符出现1次,即*表示当前字符出现1次,则str=str+1,pattern=pattern+2;
                //3、模式串当前字符出现2次或2次以上,即*表示当前字符出现2次或以上,则str=str+1,pattern=pattern;
                return matchCore(str, strIndex, pattern, patternIndex + 2)//*匹配0个,跳过
                        || matchCore(str, strIndex + 1, pattern, patternIndex + 2)//*匹配1个,跳过
                        || matchCore(str, strIndex + 1, pattern, patternIndex);//*匹配1个,再匹配str中的下一个
            } else {
                //直接跳过*(*匹配到0个)
                 //如果当前字符不匹配,则只能让*表示当前字符出现0次,则str=str,pattern=pattern+2;
                return matchCore(str, strIndex, pattern, patternIndex + 2);
            }
        }

      //模式串下一字符不为*
        if (pattern[patternIndex] == str[strIndex] || (pattern[patternIndex] == '.' && strIndex != str.length)) {
            return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
        }

        return false;
    }
}

54、表示数值的字符串

题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

思路1:正则表达式

public class Solution {
    public boolean isNumeric(char[] str) {
        String string = String.valueOf(str);
        return string.matches("[\\+-]?[0-9]*(\\.[0-9]*)?([eE][\\+-]?[0-9]+)?");
    }
}

思路2:

public class Solution {
	boolean isNumeric(char[] s) {
		if (s.length == 0)
			return false;
		if ((s.length == 1) && (s[0] < '0' || s[0] > '9'))
			return false;
		if (s[0] == '+' || s[0] == '-') {
			if (s.length == 2 && (s[1] == '.'))
				return false;
		} else if ((s[0] < '0' || s[0] > '9') && s[0] != '.')
			return false;// 首位既不是符号也不是数字还不是小数点,当然是false
		int i = 1;
		while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
			i++;
		if (i < s.length && s[i] == '.') {
			i++;
			// if(i>=s.length) return false;
			while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
				i++;
		}
		if (i < s.length && (s[i] == 'e' || s[i] == 'E')) {
			i++;
			if ((i < s.length) && (s[i] == '+' || s[i] == '-')) {
				i++;
				if (i < s.length)
					while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
						i++;
				else
					return false;
			} else if (i < s.length) {
				while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
					i++;
			} else
				return false;
		}
		if (i < s.length)
			return false;
		return true;
	}
}

55、字符流中第一个不重复的字符

题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

import java.util.*;
public class Solution {
    HashMap<Character, Integer> map=new HashMap();
    ArrayList<Character> list=new ArrayList<Character>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        if(map.containsKey(ch)){
            map.put(ch,map.get(ch)+1);
        }else{
            map.put(ch,1);
        }
         
        list.add(ch);
    }
     
    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {   char c='#';
        for(char key : list){
            if(map.get(key)==1){
                c=key;
                break;
            }
        }
         
        return c;
    }
}

56、链表中环的入口结点

题目描述
一个链表中包含环,请找出该链表的环的入口结点。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
 */
public class Solution {

	public ListNode EntryNodeOfLoop(ListNode pHead) {
		if (pHead == null || pHead.next == null)
			return null;
		// 先判断是否有环
		ListNode n1 = pHead;// 走一步
		ListNode n2 = pHead;// 走两步
		ListNode n = null;// 记录n1,n2碰面的点
		while (n2 != null && n2.next != null) {
			n2 = n2.next.next;
			n1 = n1.next;
			if (n2 == n1) {
				n = n2;// 记录碰头节点
				break;
			}
		}
		// 求出环中节点数量
		int num = 0;
		ListNode temp = n;// n的镜像
		do {
			temp = temp.next;
			num++;
		} while (temp != n);

		ListNode node1 = pHead;
		ListNode node2 = pHead;
		// node1先走num步,然后node1,node2同时走,碰头的地方即入口节点
		for (int i = 0; i < num; i++) {
			node1 = node1.next;
		}
		int num1 = 0;

		while (node1 != node2) {
			node1 = node1.next;
			node2 = node2.next;
			num1++;
		}
		return node1;
	}
}

57、删除链表中重复的结点

题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路:重点是第一个也可能是重复的点,因此新建一个preNode节点保存前一个节点

/**
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if(pHead==null)return null;
         
        ListNode preNode = null;
        ListNode node = pHead;
        while(node!=null){
            ListNode nextNode = node.next;
            boolean needDelete = false;//判断相邻两个点是否相等
            if(nextNode!=null&&nextNode.val==node.val){
                needDelete = true;
            }
            if(!needDelete){
                preNode = node;
                node = node.next;
            }else{
                int value = node.val;
                ListNode toBeDel = node;
                while(toBeDel!=null&&toBeDel.val == value){
                    nextNode = toBeDel.next;
                    toBeDel = nextNode;
                    //此处不能少,找到第一个pHead,以后的preNode就不为null了
                    if(preNode==null)
                        pHead = nextNode;
                    else
                        preNode.next = nextNode;
                    node = nextNode;
                }
            }
        }
         
        return pHead;
    }
}

58、二叉树的下一个结点

题目描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

思路:画图分析,考虑三种情况
这里写图片描述

/**
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode parent= null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    TreeLinkNode GetNext(TreeLinkNode node)
    {
        if(node==null) return null;
        if(node.right!=null){    //如果有右子树,则找右子树的最左节点
            node = node.right;
            while(node.left!=null) node = node.left;
            return node;
        }
        while(node.parent!=null){ //没右子树,则找第一个当前节点是父节点左孩子的节点
            if(node.parent.left==node) return node.parent;
            node = node.parent;
        }
        return null;   //退到了根节点仍没找到,则返回null
    }
}

59、对称的二叉树

题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {    
        return isSymmetrical(pRoot,pRoot);
    }
   
    //定义两个遍历,一个前序遍历,一个是和前序遍历相反的,先右后左的遍历
    boolean isSymmetrical(TreeNode pRoot1, TreeNode pRoot2) {
        if (pRoot1 == null && pRoot2 == null)
            return true;
         
        if (pRoot1 == null || pRoot2 == null)
            return false;
        
        if (pRoot1.val != pRoot2.val)
            return false;
              
        return isSymmetrical(pRoot1.left,pRoot2.right) && isSymmetrical(pRoot1.right, pRoot2.left);
    }
}

60、把二叉树打印成多行

题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

import java.util.*;

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(pRoot == null){
            return result;
        }
        //使用队列,先进先出
        Queue<TreeNode> layer = new LinkedList<TreeNode>();
        ArrayList<Integer> layerList = new ArrayList<Integer>();
        layer.add(pRoot);
        int start = 0, end = 1;//start记录本层打印了多少个,end记录下一层要打印多少个
        while(!layer.isEmpty()){
            TreeNode cur = layer.remove();
            layerList.add(cur.val);//添加本行打印的List里
            start++;
            //每打印一个节点,就把此节点的下一层的左右节点加入队列,并记录下一层要打印的个数
            if(cur.left!=null){
                layer.add(cur.left);           
            }
            if(cur.right!=null){
                layer.add(cur.right);
            }
            //本层打印完毕
            if(start == end){
                end = layer.size();
                start = 0;
                result.add(layerList);
                layerList = new ArrayList<Integer>();
            }
        }
        return result;
    }
}

61、按之字形顺序打印二叉树

题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

思路1:按上一题的方式,还是用队列,用标记倒叙输出,但是有缺点

import java.util.*;

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
 /*按层序遍历分层打印的代码,添加一段判断用以倒序输出即可*/
public class Solution {   
 /**
 * 缺点:将每层的数据存进ArrayList中,偶数层时进行reverse操作,但是在海量数据时,这样效率太低了。
 */
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(pRoot == null){
            return result;
        }
        boolean leftToRight = true;
        Queue<TreeNode> layer = new LinkedList<TreeNode>();
        ArrayList<Integer> layerList = new ArrayList<Integer>();
        layer.add(pRoot);
        int start = 0, end = 1;
        while(!layer.isEmpty()){
            TreeNode cur = layer.remove();
            layerList.add(cur.val);
            start++;
            if(cur.left!=null){
                layer.add(cur.left);          
            }
            if(cur.right!=null){
                layer.add(cur.right);
            }
            if(start == end){
                end = layer.size();
                start = 0;             
                if(!leftToRight){
                    result.add(reverse(layerList));
                }else{
                    result.add(layerList);
                }
                leftToRight = !leftToRight;
                layerList = new ArrayList<Integer>();
            }
        }
        return result;
    }

    public ArrayList reverse(ArrayList<Integer> layerList) {    
        int length = layerList.size();
        ArrayList<Integer> reverseList = new ArrayList<Integer>();
        for(int i = length-1; i >= 0;i--){
            reverseList.add(layerList.get(i));
        }
        return reverseList;
    }

}

思路1改进:用LinkedList,可以双向遍历,

Iterator iter = iter = queue.iterator();//从前往后遍历
Iterator iter = queue.descendingIterator();//从后往前遍历

import java.util.*;

public class Solution {
 /**
 * 思路:利用Java中的LinkedList的底层实现是双向链表的特点。
 *     1)可用做队列,实现树的层次遍历
 *     2)可双向遍历,奇数层时从前向后遍历,偶数层时从后向前遍历
 */
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
        if (pRoot == null) {
            return ret;
        }
        ArrayList<Integer> list = new ArrayList<>();
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.addLast(null);//层分隔符
        queue.addLast(pRoot);
        boolean leftToRight = true;

        while (queue.size() != 1) {
            TreeNode node = queue.removeFirst();//删除第一个,返回删除节点,若此节点不是分层的节点null,则添加左右子节点
            if (node == null) {//判断删除的节点是否为null,以此来判断是否到达分层隔符
                Iterator<TreeNode> iter = null;
                if (leftToRight) {
                    iter = queue.iterator();//从前往后遍历
                } else {
                    iter = queue.descendingIterator();//从后往前遍历
                }
                leftToRight = !leftToRight;
                while (iter.hasNext()) {
                    TreeNode temp = (TreeNode)iter.next();
                    list.add(temp.val);
                }
                ret.add(new ArrayList<Integer>(list));
                list.clear();
                queue.addLast(null);//添加层分隔符
                continue;//一定要continue
            }
            if (node.left != null) {
                queue.addLast(node.left);
            }
            if (node.right != null) {
                queue.addLast(node.right);
            }
        }

        return ret;
    }

}

思路2:用栈,先进后出,但是注意要用两个栈,因为如果用一个的话,本层的节点会压在最底下,此节点的子节点会放在最上边

import java.util.*;

public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
    ArrayList list=new ArrayList<ArrayList<Integer>>();
    if(pRoot==null)
        return list;
    Stack s1=new Stack();//从左到右输出,所以添加时先加右,再加左
    Stack s2=new Stack();//从右到左输出,所以添加时先加左,再加右
    s1.push(pRoot);
    ArrayList arr=new ArrayList<Integer>();      
       while(true){
           while(s1.size()!=0){
               TreeNode node=(TreeNode)s1.pop();
               arr.add(node.val);
               if(node.left!=null)
                   s2.push(node.left);
               if(node.right!=null)
                   s2.push(node.right);
           }
           list.add(arr);
           arr=new ArrayList<Integer>();
           if(s1.size()==0&&s2.size()==0)
               break;
           while(s2.size()!=0){
                TreeNode node1=(TreeNode)s2.pop();
               arr.add(node1.val);
               if(node1.right!=null)
                   s1.push(node1.right);
               if(node1.left!=null)
                   s1.push(node1.left);
           }
           list.add(arr);
           arr=new ArrayList<Integer>();
              
           if(s1.size()==0&&s2.size()==0)
               break;        
       }
           
        return list;
    }

}

62、序列化二叉树

题目描述
请实现两个函数,分别用来序列化和反序列化二叉树

思路:通过前序遍历的顺序,但是修改了一下,子节点为null的用#表示

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null; 
    public TreeNode(int val) {
        this.val = val; 
    } 
}
*/

public class Solution {
    public int index = -1;
 
     //序列化  
    public String Serialize(TreeNode root) {
        StringBuffer sb = new StringBuffer();
        if(root == null){
            sb.append("#,");
            return sb.toString();
        }
        sb.append(root.val + ",");
        sb.append(Serialize(root.left));
        sb.append(Serialize(root.right));
        return sb.toString();
  	}
    
    //反序列化 
    public TreeNode Deserialize(String str) {
        index++;//数组指数,每次移下一位
       int len = str.length();
        if(index >= len){
            return null;
        }
        String[] strr = str.split(",");
        TreeNode node = null;
        if(!strr[index].equals("#")){
            node = new TreeNode(Integer.valueOf(strr[index]));
            node.left = Deserialize(str);
            node.right = Deserialize(str);
        }        
        return node;
  }
}

63、二叉搜索树的第K个节点

题目描述
给定一颗二叉搜索树,请找出其中的第k大的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。

思路:中序遍历就是二叉搜索树的排序,不用递归的程序

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        if(pRoot==null||k==0) return null;
        int t=0;
        while(pRoot!=null ||stack.size()>0){
            while(pRoot!=null){
                stack.push(pRoot);
                pRoot = pRoot.left;
            }
            if(stack.size()>0){
                pRoot= stack.pop();
                t++;
                if(t==k) return pRoot;
                pRoot= pRoot.right;
            }
        }
       return null;   
    }
}

64、数据流中的中位数

题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

import java.util.*;
public class Solution{
    private Heap maxHeap = new Heap(Heap.isMaxHeap);
    private Heap minHeap = new Heap(Heap.isMinHeap);
    /**
     * 插入有两种思路:
     * 1:直接插入大堆中,之后若两堆尺寸之差大于1(也就是2),则从大堆中弹出堆顶元素并插入到小堆中
     * 若两队之差不大于1,则直接插入大堆中即可。
     * 2:奇数个数插入到大堆中,偶数个数插入到小堆中,
     * 但是 可能会出现当前待插入的数比小堆堆顶元素大,此时需要将元素先插入到小堆,然后将小堆堆顶元素弹出并插入到大堆中
     * 对于偶数时插入小堆的情况,一样的道理。why?
     * 因为要保证最大堆的元素要比最小堆的元素都要小。
     * @param num
     */
    public void Insert(Integer num) {
        //若总尺寸为偶数,则插入大顶堆中
        if(((maxHeap.size() + minHeap.size()) & 1) == 0){
            if(minHeap.size() != 0 && num > minHeap.peek()){
                minHeap.add(num);
                maxHeap.add(minHeap.pop());
            }else{
                maxHeap.add(num);
            }
        }else{
            if(maxHeap.size() != 0 && num < maxHeap.peek()){
                maxHeap.add(num);
                minHeap.add(maxHeap.pop());
            }else{
                minHeap.add(num);
            }
        }
    }
    public Double GetMedian() {
        double res = 0.0;
        if(((maxHeap.size() + minHeap.size()) & 1) == 0){
            res = (maxHeap.peek() + minHeap.peek()) / 2.0;
        }else{
            res = maxHeap.peek();
        }
        return res;
    }
    
    //堆类,可直接设置最大堆最小堆
    class Heap {
        public List<Integer> list = null;
        public static final boolean isMaxHeap = true;
        public static final boolean isMinHeap = false;
        private boolean flag = true;  //true表示最大堆,false表示最小堆
        public Heap(){
            this.list = new ArrayList<Integer>();
        }
        public Heap(boolean flag){
            this.list = new ArrayList<Integer>();
            this.flag = flag;
        }
        //获取堆大小
        public int size(){
            return this.list.size();
        }
        //获取堆顶元素
        public int peek(){
            if(list.size() == 0) return 0;
            return list.get(0);
        }
        //插入元素,从插入点开始向上调整堆
        public void add(int val){
            this.list.add(val);
            int i = list.size() - 1, index, parent, cur;
            while(i > 0){
                index = (i - 1) / 2;
                parent = list.get(index);
                cur = list.get(i);
                if(flag == true && parent < cur){
                    swap(index, i);
                }else if(flag == false && parent > cur){
                    swap(index, i);
                }
                i = index;
            }
        }
        /**
         * 将堆顶元素取出,并重新调整堆。
         * 1>取出堆顶元素
         * 2>将最后一个元素放到堆顶
         * 3>向下调整堆
         */
        public int pop(){
            if(list.size() == 0) return -1;
            int res = list.get(0);
            list.set(0,list.get(list.size() - 1));
            list.remove(list.size()-1);
            int len = list.size() - 1 , i = 0;
            int left , right;
            while(i < len){
                left = (i << 1) + 1;
                right= (i << 1) + 2;
                int maxIndex = i;
                if(flag == true){
                    if(left < len && list.get(left) > list.get(maxIndex)) maxIndex = left;
                    if(right< len && list.get(right)> list.get(maxIndex)) maxIndex = right;
                }else{
                    if(left < len && list.get(left) < list.get(maxIndex)) maxIndex = left;
                    if(right< len && list.get(right)< list.get(maxIndex)) maxIndex = right;
                }
                if(maxIndex != i){
                    swap(maxIndex,i);
                    i = maxIndex;
                }else break;
            }
            return res;
        }
        //交换list中两个位置的元素
        public void swap(int i, int j){
            int temp = list.get(i);
            list.set(i, list.get(j));
            list.set(j,temp);
        }
    }
    
}

思路2:

import java.util.ArrayList;
import java.util.Comparator;
 
/**
 * 数据流中的中位数
 *
 * @author 过路的守望
 *
 */
public class Solution {
 
    /*
     * 最大堆
     */
    private Heap maxHeap = new Heap(new Comparator<Integer>() {
 
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    });
    /*
     * 最小堆
     */
    private Heap minHeap = new Heap(new Comparator<Integer>() {
 
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });
 
    /*
     * 插入元素
     */
    public void Insert(Integer num) {
        /*
         * 如果最大堆中的元素个数大于等于最小堆中的元素个数,则新元素插入到最小堆中
         */
        if (maxHeap.getSize() >= minHeap.getSize()) {
            /*
             * 如果最小堆中未插入元素,则新元素直接插入
             */
            if (minHeap.getSize() == 0) {
                minHeap.add(num);
            }
            /*
             * 若最小堆中已存在元素,则将带插入元素与最大堆中的堆顶元素相比较,若待插入元素较小,则将最大堆堆顶元素弹出后添加到最小堆中,
             * 最大堆中将待插入元素添加进去。
             */
            else if (num < maxHeap.peek()) {
                minHeap.add(maxHeap.pop());
                maxHeap.add(num);
            }
            /*
             * 待插入元素不小于最大堆堆顶元素
             */
            else {
                minHeap.add(num);
            }
        } else {
            /*
             * 如果最大堆中未插入元素,则新元素直接插入
             */
            if (maxHeap.getSize() == 0) {
                maxHeap.add(num);
            }
            /*
             * 若最大堆中已存在元素,则将带插入元素与最小堆中的堆顶元素相比较,若待插入元素较大,则将最小堆堆顶元素弹出后添加到最大堆中,
             * 最小堆中将待插入元素添加进去。
             */
            else if (num > minHeap.peek()) {
                maxHeap.add(minHeap.pop());
                minHeap.add(num);
            }
            /*
             * 待插入元素不大于最小堆堆顶元素
             */
            else {
                maxHeap.add(num);
            }
        }
    }
 
    /*
     * 得到中间值
     */
    public Double GetMedian() {
        /*
         * 判断数据数目奇偶
         */
        int size = maxHeap.getSize() + minHeap.getSize();
        if ((size & 1) == 1) {
            return (double) minHeap.peek();
        }
        return (maxHeap.peek() + minHeap.peek()) / 2.0;
    }
 
}
 
/**
 * 数据结构-堆
 *
 * @author 过路的守望
 *
 */
class Heap {
 
    /*
     * 比较器
     */
    private Comparator<Integer> comparator;
    private ArrayList<Integer> list;
 
    public Heap() {
        list = new ArrayList<Integer>();
    }
 
    /*
     * 构造器传入比较器
     */
    public Heap(Comparator<Integer> comparator) {
        this();
        this.comparator = comparator;
    }
 
    /*
     * 弹出堆顶元素
     */
    public int pop() {
        int data = list.get(0);
        list.set(0, list.remove(list.size() - 1));
        percolateDown();
        return data;
    }
 
    /*
     * 返回堆顶元素
     */
    public int peek() {
        return list.get(0);
    }
 
    /*
     * 添加元素
     */
    public void add(int element) {
        list.add(element);
        percolateUp();
        return;
    }
 
    /*
     * 堆中对象个数
     */
    public int getSize() {
        return list.size();
    }
 
    /*
     * 下滤操作
     */
    private void percolateDown() {
        int size = list.size();
        /*
         * 从下标为0的节点开始下滤
         */
        int i = 0;
        /*
         * temp保存最大堆或最小堆的堆顶值
         */
        int temp = list.get(0);
        int leftChild = getLeftChild(i);
        while (leftChild < size) {
            /*
             * 得到左右儿子中较大的下标
             */
            if (leftChild < size - 1
                    && comparator.compare(list.get(leftChild),
                            list.get(leftChild + 1)) < 0) {
                leftChild++;
            }
            /*
             * 若儿子大于父亲,就把 儿子的值赋给父亲
             */
            if (comparator.compare(temp, list.get(leftChild)) < 0) {
                list.set(i, list.get(leftChild));
                i = leftChild;
                leftChild = getLeftChild(i);
                continue;
            } else {
                break;
            }
        }
        /*
         * 下滤完成,找到temp所在下标
         */
        list.set(i, temp);
    }
 
    /*
     * 上滤操作
     */
    private void percolateUp() {
        /*
         * 从堆中最后一个元素开始上滤
         */
        int i = list.size() - 1;
        int temp = list.get(i);
        int parent = getParent(i);
        while (parent >= 0) {
            /*
             * 若父亲小于儿子,则把父亲的值赋给儿子
             */
            if (comparator.compare(temp, list.get(parent)) > 0) {
                list.set(i, list.get(parent));
                i = parent;
                parent = getParent(parent);
                continue;
            } else {
                break;
            }
        }
        /*
         * 上滤完成,找到temp所在下标
         */
        list.set(i, temp);
    }
 
    /*
     * 左儿子下标
     */
    private int getLeftChild(int i) {
        return 2 * i + 1;
    }
 
    /*
     * 父亲下标
     */
    private int getParent(int i) {
        return (int) Math.floor((i - 1) / 2.0);
    }
}

65、滑动窗口的最大值

题目描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

import java.util.*;
/**
 * 题目:滑动窗口的最大值
 * 思路:滑动窗口应当是队列,但为了得到滑动窗口的最大值,队列序可以从两端删除元素,因此使用双端队列。
 *     原则:
 *     对新来的元素k,将其与双端队列中的元素相比较
 *     1)前面比k小的,直接移出队列(因为不再可能成为后面滑动窗口的最大值了!),
 *     2)前面比k大的X,比较两者下标,判断X是否已不在窗口之内,不在了,直接移出队列
 *     队列的第一个元素是滑动窗口中的最大值
 */
public class Solution {
     
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
         
        ArrayList<Integer> ret = new ArrayList<>();
        if (num == null) {
            return ret;
        }
        if (num.length < size || size < 1) {
            return ret;
        }
        // 用来保存可能是滑动窗口最大值的数字的下标 
        LinkedList<Integer> indexDeque = new LinkedList<>();
        for (int i = 0; i < size - 1; i++) {
            // 如果已有数字小于待存入的数据,
                // 这些数字已经不可能是滑动窗口的最大值
                // 因此它们将会依次地从队尾删除
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
        }
         
        for (int i = size - 1; i < num.length; i++) {
            // 如果已有数字小于待存入的数据,
                // 这些数字已经不可能是滑动窗口的最大值
                // 因此它们将会依次地从队尾删除
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
            if (i - indexDeque.getFirst() + 1 > size) {
                indexDeque.removeFirst();
            }
            ret.add(num[indexDeque.getFirst()]);
        }
        return ret;
    }
}
  • 27
    点赞
  • 106
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值