算法(面试题)
输入一个链表,反转链表后,输出新链表的表头。
输入{1,2,3}返回值{3,2,1}
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/ // 结构体(链表节点)
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null) {
return null;
}
ListNode newHead=null;
ListNode cur=head;
ListNode pre=null;
while(cur!=null) {
ListNode curNext=cur.next;
if(curNext==null) {
newHead=cur;
}
cur.next=pre;
pre=cur;
cur=curNext;
}
return newHead;
}
}
提高时间效率的一个常用方法就是牺牲空间换取时间,这里也可以使用这种办法。我们可以定义一个辅助栈minStack,帮助我们记录最小值。在我们的类中,需要有两个栈,一个就是我们用来存值的栈dataStack,另外一个就是帮助我们维护最小值的栈minStack。
push入栈操作有以下两种情况:
• dataStack为空:此时,栈中没有元素,我们将push传入的参数直接放入到dataStack以及minStack中;
• dataStack不为空:此时,将push操作传入的参数先放入dataStack中,然后判断这个元素与minStack的栈顶元素谁更大,若这个参数小于或等于minStack的栈顶元素,我们就将它加入到minStack中,否则minStack不变;
这样,我们就可以保证,minStack的栈顶元素,一定是当前栈中最小的元素,而当我们调用min方法时,直接返回minStack的栈顶元素就行了。
与push操作相对应的,pop出栈操作,也有两种情况:
• 出栈的元素大于栈中最小值:此时dataStack的栈顶元素出栈,而minStack不变;
• 出栈的元素等于栈中最小值:此时dataStack的栈顶元素出栈,同时,minStack的栈顶元素也出栈;
public class Solution {
private Stack<Integer> dataStack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();
/**
* 入栈操作
*/
public void push(int node) {
// 判断是否需要更新minStack
if(dataStack.isEmpty() || minStack.peek() >= node) {
minStack.push(node);
}
// 将元素放入dataStack
dataStack.push(node);
}
/**
* 出栈操作
*/
public void pop() {
// 若栈不为空才执行出栈
if(!dataStack.isEmpty()) {
// 若当前出栈的元素,等于栈中的最小值(即minStack的栈顶)
// 则minStack的栈顶出栈
if(dataStack.pop() == minStack.peek()) {
minStack.pop();
}
}
}
/**
* 查看栈顶元素
*/
public int top() {
return dataStack.peek();
}
/**
* 返回栈中最小值
*/
public int min() {
// 返回最小值,即minStack的栈顶元素
return minStack.peek();
}
}
给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。
示例1
输入
[[1,1,0,0,0],[0,1,0,1,1],[0,0,0,1,1],[0,0,0,0,0],[0,0,1,1,1]]
返回值
3
仓库:Warehouse
public class Warehouse {
private volatile int apple = 0;
public synchronized void increace() {
while (apple == 5) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple++;
System.out.println("苹果生产成功!");
notify();
}
public synchronized void decreace() {
while (apple == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple--;
System.out.println("苹果消费成功!");
notify();
}
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
Consumer con = new Consumer(warehouse);
Producer pro = new Producer(warehouse);
Thread t1 = new Thread(con);
Thread t2 = new Thread(pro);
t1.start();
t2.start();
}
}
生产者:Producer
public class Producer implements Runnable {
private Warehouse warehouse;
public Producer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for( int i=0;i<10;i++)
{
try {
System. out .println("pro i:" +i);
Thread. sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
warehouse.increace();
}
}
}
消费者:Consumer
public class Consumer implements Runnable {
private Warehouse warehouse;
public Consumer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for( int i=0;i<10;i++)
{
try {
System. out .println("Con: i " +i);
// 这里设置跟上面30不同是为了 仓库中的苹果能够增加,不会生产一个马上被消费
Thread. sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
warehouse.decreace();
}
}
}
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则是稳定的
1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
public class demo_sort {
public static void main(String[] args) {
//冒泡排序算法
int[] numbers=new int[]{1,5,8,2,3,9,4};
//需进行length-1次冒泡
for(int i=0;i<numbers.length-1;i++)
{
for(int j=0;j<numbers.length-1-i;j++)
{
if(numbers[j]>numbers[j+1])
{
int temp=numbers[j];
numbers[j]=numbers[j+1];
numbers[j+1]=temp;
}
}
}
System.out.println("从小到大排序后的结果是:");
for(int i=0;i<numbers.length;i++)
System.out.print(numbers[i]+" ");
}
}
(2)选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
public static void main(String[] args) {
int[] arr={1,3,2,45,65,33,12};
System.out.println("交换之前:");
for(int num:arr){
System.out.print(num+" ");
}
//选择排序的优化
for(int i = 0; i < arr.length - 1; i++) {// 做第i趟排序
int k = i;
for(int j = k + 1; j < arr.length; j++){// 选最小的记录
if(arr[j] < arr[k]){
k = j; //记下目前找到的最小值所在的位置
}
}
//在内层循环结束,也就是找到本轮循环的最小的数以后,再进行交换
if(i != k){ //交换a[i]和a[k]
int temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}
}
System.out.println();
System.out.println("交换后:");
for(int num:arr){
System.out.print(num+" ");
}
}
(3)插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
public static int[] sort(int[] ins){
for(int i=1; i<ins.length; i++){
for(int j=i; j>0; j--){
if(ins[j]<ins[j-1]){
int temp = ins[j-1];
ins[j-1] = ins[j];
ins[j] = temp;
}
}
}
return ins;
}
(4)快速排序
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j,交换a[i]和a[j],重复上面的过程,直到i > j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。
详细请看快速排序
public class QuickSort {
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
前序,中序,后序,层序遍历