/offer

算法习题

Primary LanguageJava

位运算

异或运算==》无符号加
a^a=0 a^0=a
取二进制最右边的1 int count = a & ((~a) + 1)

数组中有一个数出现奇数次,其他出现偶数次,求这个数。

//将所有数进行异或的结果即为所求

数组中有两个数出现奇数次,其他出现偶数次,求这个数。

//所有数异或后的结果为 a ^ b
可知a!=b,所以e = a ^ b != 0,所以ab在某位上只有个数为1
int i = e & ((~e) + 1)找到最右边的1,假设a在该位上为1
下面用i找出a
新增个中间变量e‘,将数组中的数与i进行异或,如果异或结果为0,则把该数和e’与该数异或,因为除了a之外,其他这个位上为1的数的个数是偶数个,异或完后都会抵消,最后e‘=a,则b=a ^ a ^ b = e ^ a = e ^ e

一个数的二进制中1的个数。

int count = a & ((~a) + 1) 还是利用这个式子
public class OneCount {

    public static void main(String[] args) {
        System.out.println(findOneCount(7));
    }

    private static int findOneCount(int N) {
        int i;
        int result = 0;
        while ((i = (N & ((~N) + 1))) != 0) {
            result ++;
            N ^= i;
        }
        return result;
    }
}

用栈实现队列

O(1)复杂度获取栈中最小值

队列

用队列实现栈

环形队列

public class CircularQueue {
    private int[] table;
    private int size = 0; //元素个数
    private int front = 0; //队头
    private int rear = 0; //队尾
    private final int limit;

    private static final int DEFAULT_LIMIT = 10;

    public CircularQueue() {
        this.limit = DEFAULT_LIMIT;
        table = new int[limit];
    }

    public CircularQueue(int limit) {
        this.limit = limit;
        table = new int[limit];
    }

    public void put(int val) {
        if (size == limit) {
            throw new RuntimeException("当前队列已满");
        }
        size ++;
        table[rear] = val;
        rear = rear == limit - 1 ? 0 : rear + 1;
    }

    public int pop() {
        if (size == 0) {
            throw new RuntimeException("当前队列为空");
        }
        size --;
        int ret = table[front];
        front = front == limit - 1 ? 0 : front + 1;
        return ret;
    }

    public boolean isEmpty() {
        return size == 0;
    }
}

hash

递归

master公式

形如T(N) = a * T(N/b) + O(N^d)(其中的a、b、d都是常数)的递归函数,可以直接通过Master公式来确定时间复杂度
如果 log(b,a) < d,复杂度为O(N^d)
如果 log(b,a) > d,复杂度为O(N^log(b,a))
如果 log(b,a) == d,复杂度为O(N^d * logN)

归并排序

    private void mergeSplit(T[] nums, int l, int r) {
        if (l == r) {
            return;
        }
        int mid = l + ((r - l) >> 1);
        mergeSplit(nums, l, mid);
        mergeSplit(nums, mid + 1, r);
        merge(nums, l, mid, r);
    }

    private void merge(T[] nums, int l, int mid, int r) {
        int left = l;
        int right = mid + 1;
        List<T> temp = new ArrayList<>(r - l + 1);
        while (left <= mid && right <= r) {
            if (less(nums[left], nums[right])) {
                temp.add(nums[left++]);
            } else {
                temp.add(nums[right++]);
            }
        }
        while (left <= mid) {
            temp.add(nums[left++]);
        }
        while (right <= r) {
            temp.add(nums[right++]);
        }
        for (int i = 0; i < temp.size(); i++) {
            nums[l + i] = temp.get(i);
        }
    }

归并**可解决 找某个数左边或右边某种数的问题

  1. 小数和 把一个数的左边小于该数的数叫小数,求所有小数的和。
    eg: [1,3,4,2,5]
    1没有小数
    3的小数:1
    4的小数:1,3
    2的小数:1
    5的小数:1,3,4,2
    小数和为:1+1+3+1+1+3+4+2

前缀树

1)单个字符串中,字符从前到后的加到一棵多叉树上
2)字符放在边上,节点上有专属的数据项(常见的是pass和end值)
3)样本添加方式,每个字符串都从根节点开始加,如果没有路就新建,如果有路就复用
4)添加时,沿途节点的pass值加1,每个字符串结束时来到的节点end值加1
end值可查某个字符串出现几次,pass值可查以某个字符串为前缀的字符串出现过几次

//将字符串插入前缀树中
public void insert(String str) {
    if (str == null) {
        return;
    }
    Node node = head;
    node.pass++;
    char[] chars = str.toCharArray();
    for (char aChar : chars) {
        int index = aChar - 'a';//在哪个节点上
        if (node.next[index] == null) {
            node.next[index] = new Node();
        }
        node.next[index].pass++;
        node = node.next[index];
    }
    node.end++;
}

//str出现过几次
public Integer search(String str) {
    if (str == null) {
        return 0;
    }
    Node node = head;
    char[] chars = str.toCharArray();
    for (char aChar : chars) {
        int index = aChar - 'a';//在哪个节点上
        if (node.next[index] == null) {
            return 0;
        }
        node = node.next[index];
    }
    return node.end;
}

public void delete(String str) {
    if (search(str) > 0) {
        Node node = head;
        node.pass--;
        char[] chars = str.toCharArray();
        for (char aChar : chars) {
            int index = aChar - 'a';//在哪个节点上
            if (--node.next[index].pass == 0) {//pass为0后,后面节点直接丢弃
                node.next[index] = null;
                return;
            }
            node = node.next[index];
        }
        node.end--;
    }
}

//以prefix为前缀的字符串个数
public Integer prefixNumber(String prefix) {
    if (prefix == null) {
        return 0;
    }
    Node node = head;
    char[] chars = prefix.toCharArray();
    for (char aChar : chars) {
        int index = aChar - 'a';//在哪个节点上
        if (node.next[index] == null) {
            return 0;
        }
        node = node.next[index];
    }
    return node.pass;
}

单链表

1.是否有环,找出入环节点
快慢指针(快指针一次2步,慢指针一次1步),快指针第一次和慢指针相遇后,快指针从头开始和慢指针一同以一次1步前进,再次相遇为入环节点。

public Node hasCircle(Node head) {
    if (head == null || head.next == null || head.next.next == null) {
        return null;
    }
    Node slow = head.next, quick = head.next.next;
    while (slow != quick) {
        if (slow == null || quick.next == null) {
            return null;
        }
        slow = slow.next;
        quick = quick.next.next;
    }
    quick = head;
    while (slow != quick) {
        slow = slow.next;
        quick = quick.next;
    }
    return slow;
}