算法基础学习01

162 阅读2分钟
1. 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

1) 暴力解法,两层循环嵌套遍历,时间复杂度为O(N²),空间复杂度为O(1)

public static int[] twoSum(int[] nums,int target) {
    for (int i=0;i<nums.length;i++) {
        for (int j= i+1;j< nums.length;j++) {
            if (nums[i] + nums[j] == target) {
                return new int[]{i,j};
            }
        }
    }
    return new int[0];
}

2) 利用哈希表,数组值为key,对应的数组下标为value,时间复杂度为O(N),空间复杂度为O(N)

public static int[] twoSum(int[] nums,int target) {
    
    Map<Integer,Integer> hashTable = new HashMap<Integer,Integer>();
    for (int i=0;i<nums.length;i++) {
        int firstValue = target - nums[i];
        if (hashTable.containsKey(firstValue)) {
            return new int[]{hashTable.get(firstValue),i};
        }
        hashTable.put(nums[i],i);
    }
    return new int[0];
}
2,一个数组中只有一种数出现了奇数次,其他都出现了偶数次,怎么找到并打印这种数

此题目考察的是异或运算(^)满足交换律和结合律

异或运算(^):相同为0,不同为1

public static void printOddNumOne(int[] arr) {
    int result = 0;
    for (int i=0;i<arr.length;i++) {
        result ^= arr[i];
    }
    System.out.print(result);
}
3,一个数组中只有两种数出现了奇数次,其他都出现了偶数次,怎么找到并打印这两种数

此题目考察的知识点是位运算

异或运算(^):相同为0,不同为1

0&0=0;   0&1=1;   1&0=1;    1&1=0;

与运算(&):两位同时为1,则值为1。否则为0

0&0=0;   0&1=1;   1&0=1;    1&1=1;

首先如何提取出一个数N的二进制位的最右边的1? anwser = N & (~N + 1)
eg.

N == 0101 0110

~N == 1010 1001

(~N+1) == 1010 1010

N&(~N+1)== 0000 0010 (注意:+1只是在最末位+1操作,最多只是最后两位发生变化)

public static void printOddNum2(int[] arr) {
    int eor = 0;
    for (int i=0;i<arr.length;i++) {
        eor ^= arr[i];
    }
    //结果是 eor = a ^ b 且 eor ≠ 0,因此eor必然有一个位置上是1
    int rightOne = eor & (~eor + 1); //提取eor上最右侧的1,也就是说其余位均为0
    int onlyOne = 0;
    for (int i=0;i<arr.length;i++) {
        if ((arr[i] & rightOne) != 0) {
            onlyOne ^= arr[i];
        }
    }
    // onlyOne = a 或者 b,所以onlyOne ^ eor = onlyOne ^ a ^ b = b 或者 a
    System.out.print("one:" + onlyOne);
    System.out.print("another:" + (onlyOne ^ eor));
}
4,删除一个单链表中指定值的节点
public static Node removeValueNode(Node head,int num) {

    //如果head节点是指定值的节点,就往后移
    while (head != null) {
        if (head.value != num) {
            break;
        }
        head = head.next;
    }
    // head来到第一个不需要删除的位置
    Node pre = head;
    Node cur = head;

    while (cur != null) {
        if (cur.value == num) {
            pre.next = cur.next;
        } else {
            pre = cur;
        }
        cur = cur.next;
    }
    return head; //注意返回的还是head 而不是pre或cur
}
5,反转单链表和双向链表
//单链表
public static class Node {
    public int value;
    public Node next;

    public Node(int data) {
        value = data;
    }
}

//双向链表
public static class DoubleNode {
    public int value;
    public DoubleNode last;
    public DoubleNode next;

    public DoubleNode(int data) {
        value = data;
    }
}

//反转单链表
public static Node reverseLinkedList(Node head) {
    Node pre = null;
    Node next = null;

    while (head != null) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

//反转双向链表
public static DoubleNode reverseDoubleList(DoubleNode head) {
    DoubleNode pre = null;
    DoubleNode next = null;

    while (head != null) {
        next = head.next;
        head.next = pre;
        head.last = next;
        pre = head;
        head = next;
    }
    return pre;
}
6,如何用数组实现不超过固定大小的队列和栈

栈:正常使用

队列:环形数组

public static class MyQueue {
    private int[] arr;
    private int pushi;
    private int pulli;
    private int size;
    private final int limit;

    public MyQueue(int limit){
        arr = new int[limit];
        pushi = 0;
        pulli = 0;
        size = 0;
        this.limit = limit;
    }

    public void push(int value) {
        if (size == limit) {
            throw new RuntimeException("队列满了,不能再加了");
        }
        size++;
        arr[pushi] = value;
        pushi = nextIndex(pushi);
    }

    public int pop() {
        if (size == 0) {
            throw new RuntimeException("队列空了,不能再拿了");
        }
        size--;
        int ans = arr[pulli];
        pulli = nextIndex(pulli);
        return ans;
    }

    private int nextIndex(int index) {
        return index < limit - 1 ? index + 1 : 0;
    }
}
7,如何用栈来实现队列

需要申请两个栈 eg

public static class TwoStacksImplementQueue {
    public Stack<Integer> s1;
    public Stack<Integer> s2;

    public TwoStacksImplementQueue() {
        s1 = new Stack<Integer>();
        s2 = new Stack<Integer>();
    }

    //s1栈向s2栈中倒入数据
    private void s1ToS2() {
        if (s2.empty()) {
            while (!s1.empty()) {
                s2.push(s1.pop());
            }
        }
    }

    //入队
    public void add(int pushInt) {
        s1.push(pushInt);
        s1ToS2();
    }

    //出队
    public int poll() {
        if (s1.empty() && s2.empty()) {
            throw new RuntimeException("Queue is empty");
        }
        s1ToS2();
        return s2.pop();
    }

    public int peek() {
        if (s1.empty() && s2.empty()) {
            throw new RuntimeException("Queue is empty");
        }
        s1ToS2();
        return s2.peek();
    }
}
8,用递归方法求数组中的最大值
public static int getMax(int[] arr) {
    return process(arr,0,arr.length-1);
}

public static int process(int[] arr,int L, int R) {
    //base case基准条件,在递归中一定要存在
    if (L == R) { 
        return arr[L];
    }

    int mid = L + ((R - L) >> 1);
    int leftMax = process(arr,L,mid);
    int rightMax = process(arr,mid+1,R);
    return Math.max(leftMax,rightMax);
}

思考:递归函数在系统里是怎么实现的?系统栈

9,归并排序
public static void process(int[] arr,int L, int R) {
    //base case基准条件,在递归中一定要存在
    if (L == R) {
        return;
    }

    int mid = L + ((R - L) >> 1);
    process(arr,L,mid);
    process(arr,mid+1,R);
    merge(arr,L,mid,R);
}

public static void merge(int[] arr, int L,int M,int R) {
    int[] help = new int[R-L+1];
    int i=0;
    int p1 = L;
    int p2 = M + 1;

    while (p1<=M && p2 <= R) {
        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= M) {
        help[i++] = arr[p1++];
    }
    while (p2 <= R) {
        help[i++] = arr[p2++];
    }
    for (i=0;i<help.length;i++) {
        arr[L+i] = help[i];
    }

}
10,arr[L...R] 荷兰国旗问题的划分,以arr[R]做划分值
public static void netherlandsFlag(int[] arr,int L,int R) {
    if (L >= R) {
        return;
    }

    int leftFlag = L -1;
    int rightFlag = R;
    int index = L;
    while (index < rightFlag) {
        if (arr[index] < arr[R]) {
            ++leftFlag;
            swap(arr,index,leftFlag);
            index++;
        } else if (arr[index] == arr[R]) {
            index++;
        } else  {
            --rightFlag;
            swap(arr,index,rightFlag);
        }
    }
    swap(arr,rightFlag,R);

}
11,快速排序3.0
public static void quickSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    process(arr,0,arr.length-1);
}
public static void process(int[] arr,int L,int R) {
    if (L >= R) {
        return;
    }
    //随机选取一个数作为划分值,并放在了最右边
    swap(arr,L+(int)(Math.random() * (R-L + 1)),R);
    
    int[] equalArea = partition(arr,L,R);
    process(arr,L,equalArea[0]-1);
    process(arr,equalArea[1]+1,R);

}

//arr[L...R] 荷兰国旗问题的划分,以arr[R]做划分值,返回相等区域的起始位置下标组成的数组
public static int[] partition(int[] arr,int L,int R) {
    if (L > R) {
        return new int[]{-1,-1};
    }
    if (L == R) {
        return new int[]{L,R};
    }

    int leftFlag = L -1;
    int rightFlag = R;
    int index = L;
    while (index < rightFlag) {
        if (arr[index] < arr[R]) {
            ++leftFlag;
            swap(arr,index,leftFlag);
            index++;
        } else if (arr[index] == arr[R]) {
            index++;
        } else  {
            --rightFlag;
            swap(arr,index,rightFlag);
        }
    }
    swap(arr,rightFlag,R);
    return new int[]{leftFlag+1,rightFlag};
}