文章目录
螺旋矩阵
描述:
给定一个m x n大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中的所有元素。
思路:
找到一个螺旋的四个点,这四个点是用来找到螺旋的位置,以及判断是否旋到头了
注意:
- 输入为空矩阵
- 绕完一条边,下一条边的起始点应该 -1 或者 +1,不然就会重复
- 在后面两个加入判断,不在前面加判断的原因是因为在绕第一条边和第二条边的时候不会出现头和尾重合的情况
public ArrayList<Integer> spiralOrder(int[][] matrix) {
ArrayList<Integer> result = new ArrayList<>();
if(matrix.length==0){
return result;
}
int left=0;
//有多少列
int left_end = matrix[0].length - 1;
int top=0;
//有多少行
int top_end = matrix.length - 1;
while(top < (matrix.length + 1)/2 && left < (matrix[0].length + 1)/2){
for(int i = left; i<=left_end; i++){
result.add(matrix[top][i]);
}
for(int i = top+1; i<=top_end; i++){
result.add(matrix[i][left_end]);
}
for(int i = left_end-1;top!=top_end &&i>=left;i--){
result.add(matrix[top_end][i]);
}
for(int i = top_end-1; left_end!=left&&i>top; i--){
result.add(matrix[i][left]);
}
top++;
left_end--;
top_end--;
left++;
}
return result;
}
斐波那契
NC68 跳台阶
描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
- 递归(效率低,占用内存大)
public int Fibonacci(int n) {
if(n==0){
return 0;
}
else if(n==1){
return 1;
}
else{
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
- 非递归
避免重复递归计算,因此用一个数组来存放,
时间复杂度:O(n), 没有重复的计算
public int Fibonacci(int n) {
int[] arr = new int[40];
arr[1] = 1;
for(int i = 2; i <= n; i++){
arr[i] = arr[i-1] + arr[i-2];
}
return arr[n];
}
- 动态规划
时间复杂度:O(n)
空间复杂度:O(1),之前用一个数组把所有的数据都存起来,但是在n-1之前的都没有用
public int Fibonacci(int n) {
if(n==0 || n==1){
return n;
}
int a = 0,b=1,c;
for(int i=2; i<=n; i++){
c = a + b;
a = b;
b = c;
}
return c;
}
NC61 两数之和
描述:
给出一个整数数组,请在数组中找出两个加起来等于目标值的数,
你给出的函数twoSum 需要返回这两个数字的下标(index1,index2),需要满足 index1 小于index2.。注意:下标是从1开始的
假设给出的数组中只存在唯一解
例如:
给出的数组为 {20, 70, 110, 150},目标值为90
输出 index1=1, index2=2
- 暴力解法
时间复杂度:O(n^2),两层for循环
空间复杂度:O(1),没有使用额外空间
public int[] twoSum (int[] numbers, int target) {
int[] result = new int[2];
if(numbers.length < 2){
return result;
}
for(int i = 0; i < numbers.length ; i++){
for(int j = i + 1; j<numbers.length ;j++){
if(numbers[i] + numbers[j] == target){
result[0] = i + 1;
result[1] = j + 1;
return result;
}
}
}
return result;
}
- 哈希
哈希表的思想为「以空间换时间」,这是由于哈希表保存了键值对,其「查找」复杂度为O(1)。
时间复杂度:O(n) 一次遍历hash索引查找时间复杂度为O(1)
空间复杂度:O(n) 申请了n大小的map空间
因为有唯一解,因此可以理解为第二遍历的时候就能够找到这个差值,并且要保证我找到的这个值的索引和自身的不一样,比如出现 【3,2,3】 6 这样的用例输入的时候
public int[] twoSum (int[] numbers, int target) {
// write code here
HashMap<Integer,Integer> map = new HashMap<>();
for(int i = 0; i< numbers.length; i++){
map.put(numbers[i],i);
}
for(int i = 0; i<numbers.length; i++){
if(map.containsKey(target-numbers[i]) && i != map.get(target-numbers[i])){
return new int[]{i + 1, map.get(target-numbers[i]) + 1};
}
}
return null;
}
排序题
- 调用函数
public int[] MySort (int[] arr) {
//调用库函数sort,默认升序
Arrays.sort(arr);
return arr;
}
快排
- 递归
public int[] MySort (int[] arr) {
quick(arr,0,arr.length -1);
return arr;
}
public void swap(int[] arr, int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void quick(int[] arr ,int low,int high){
if(low > high){
return;
}
int i,j,pivot;
i = low;
j = high;
pivot = arr[low];
while(i<j){
while(i<j && arr[j]>pivot){
j--;
}
while(i<j && arr[i]<pivot){
i++;
}
if(i<j){
swap(arr,i,j);
}
}
quick(arr,low,j-1);
quick(arr,j+1,high);
}
public int[] MySort (int[] arr) {
quick(arr,0,arr.length -1);
return arr;
}
public void swap(int[] arr, int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void quick(int[] arr ,int low,int high){
if(low< high){
int point = partition(arr, low, high);
quick(arr,low, point-1);
quick(arr,point+1,high);
}
}
public int partition(int[] arr, int low, int high){
int first = arr[low];
while(low<high){
while(low<high && arr[high] >= first){
high--;
}
swap(arr,low,high);
while(low<high && arr[low] <= first){
low++;
}
swap(arr,low,high);
}
return low;
}
堆排
NC140 排序
上面的方法
NC119 最小的K个数
给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
0 <= k <= input.length <= 10000
0 <= input[i] <= 10000
- 排序后,再前k个值
时间复杂度:O(nlongn)
空间复杂度:O(1)
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
if(k==0){
return result;
}
Arrays.sort(input);
for(int i=0; i<k; i++){
result.add(input[i]);
}
return result;
}
查了资料看了一下 Arrays的排序,在小于47的数组为插入排序,在 [47,286)用的快排,而在大于等于286后,为归并排序。
1.数量非常小的情况下(就像上面说到的,少于47的),插入排序等可能会比快速排序更快。 所以数组少于47的会进入插入排序。
2.快排数据越无序越快(加入随机化后基本不会退化),平均常数最小,不需要额外空间,不稳定排序。
3.归排速度稳定,常数比快排略大,需要额外空间,稳定排序。
NC88 寻找第K大
思路和上面是一样的,排序,然后返回数组n-k位置上的数值
链表、数组
NC78 反转链表
描述:
输入一个链表,反转链表后,输出新链表的表头。
public ListNode ReverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while(cur!=null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
时间复杂度:O(n), 遍历一次链表
空间复杂度:O(1)
NC33 合并两个排序的链表
描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则
思路:head为新链表的头,cur作为不断添加新节点索引(具体不知道怎么描述)
肯定还会存在 list1为空,list2不为空,但是while循环执行结束的情况,因此还需要拼上后面没有连接的表(因为是list是有序的)
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head = new ListNode(0);
ListNode cur = head;
while(list1!=null&& list2!=null){
if(list2.val > list1.val){
cur.next = list1;
list1 = list1.next;
}else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if(list1!=null){
cur.next = list1;
}
if(list2!=null){
cur.next = list2;
}
return head.next;
}
时间复杂度:O(m+n),m,n分别为两个单链表的长度
空间复杂度:O(1)
NC4 判断链表中是否有环
描述:
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
你能给出空间复杂度O(1)的解法么?
思路就在理解环的意思,头尾相接为环,尾和链中任意节点相接也可以连成环。
- 一个快慢指针能够验证链表中是否存在环
使用两个指针,fast 与 slow。
它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针最终将再次与 slow 指针在环中相遇。
主要要判断 fast.next是否为空,而且为了保证效率
先判断fast是否为空,再判断fast.next是否为空,因为是短路与
public boolean hasCycle(ListNode head) {
if(head==null|| head.next == null){
return false;
}
ListNode slow = head,fast = head;
while(slow!=null && fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
return true;
}
}
return false;
}
NC22 合并两个有序的数组
描述
给出一个整数数组 和有序的整数数组 ,请将数组 合并到数组 中,变成一个有序的升序数组
- 新建一个数组
时间复杂度:O(M+N)
空间复杂度:O(m+n)
public void merge(int A[], int m, int B[], int n) {
int[] temp = new int[m+n];
int i=0,j=0,t=0;
int time = m + n;
while(i<m || j<n){
if(i==m){
temp[t++] = B[j++];
}
else if(j==n){
temp[t++] = A[i++];
}
else{
temp[t++] = A[i] > B[j] ? B[j++] :A[i++];
}
}
for(int c = 0 ; c < m+n ;c++){
A[c] = temp[c];
}
}
- 从尾部合并
因为题目就已经是说,两个数组递增数组,因此末尾都是最大值,不断的移动索引,把最大的都放从尾部放下去。
终止条件,i,j两个索引都到首部, 由于a有位置且自成有序
public void merge(int A[], int m, int B[], int n) {
int end = m + n - 1;
int i = m - 1;
int j = n - 1;
while(i >=0 && j>=0 && end>0){
if(A[i]>B[j]){
A[end--] = A[i--];
}else{
A[end--] = B[j--];
}
}
// 因为a数组肯定是位置足够的,如果是以下情况
// a = 4 5 6 0 0 0 0
// b = 1 2 3 4
// a = 456 4456
// 此时i已经为0了,那么就必定需要将b中的数据都全部放到a数组中
// a = 4 5 6 7 0 0
// b = 6 7
// 由于是有序的,那么
// a = 7 -》 7 7 -》 6 7 7 此时b的j已经结束了!,但是其实a的数组也已经排好了
while(j>=0){
A[end--] = B[j--];
}
}
NC76 用两个栈实现队列
描述
用两个栈来实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。
执行两次先进后出,这样就反转成了一个队列
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(){
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.peek());
stack1.pop();
}
}
int result = stack2.peek();
stack2.pop();
return result;
}
}
NC105 二分查找-II
描述:
请实现有重复数字的升序数组的二分查找
给定一个 元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的第一个出现的target,如果目标值存在返回下标,否则返回 -1
mid的边界条件要用 mid = left+ (right - left) / 2
而且要往左边逼近,取第一个值
public int search (int[] nums, int target) {
// write code here
if(nums.length == 0 || nums == null){
return -1;
}
int left = 0;
int right = nums.length - 1;
while(left<right){
int mid = left + (right - left) / 2 ;
if (nums[mid] < target) {
left = mid + 1;
}else{
right = mid;
}
}
return nums[left] == target ? left: -1;
}
NC19 子数组的最大累加和问题
描述:
给定一个数组arr,返回子数组的最大累加和
例如,arr = [1, -2, 3, 5, -2, 6, -1],所有子数组中,[3, 5, -2, 6]可以累加出最大的和12,所以返回12.
题目保证没有全为负数的数据
要求:时间复杂度为O(n),空间复杂度为O(1)
- 贪心算法
由局部最优得到全局最优
public int maxsumofSubarray (int[] arr) {
// write code here
int thisSum = 0;
int ans = 0;
for(int i = 0; i <arr.length; i++){
thisSum += arr[i];
if(thisSum > ans){
ans = thisSum;
}
if(thisSum < 0){
thisSum = 0;
}
}
return ans;
}