当前位置: 首页 > news >正文

数据结构和算法基础(一)

文章目录

    • 链表反转
    • 链表合并
    • 删除链表倒数第 n 个结点
    • 找链表的中间结点
    • 链表中环的检测
    • 排序算法
    • 递归

趁空闲时间刷一遍极客时间上王争的《数据结构与算法之美》课程,个人觉得写的很好,每章节由浅入深且从基础到引入设计类问题,如果写过很多代码想要进行架构设计转型时再回头看这些基础知识还蛮有趣的,以下纪录下随着课程走的部分实现代码和思考;
内容主要是笔记和代码,注:手写一遍代码是有必要的;

链表反转

单链表反转

class ListNode {  int val;  ListNode next;  ListNode(int val) {  this.val = val;  this.next = null;  }  
public ListNode reverseList(ListNode head) {  ListNode prev = null;  ListNode curr = head;  while (curr != null) {  ListNode nextTemp = curr.next;  // 临时保存下一个节点  curr.next = prev;               // 反转当前节点的指针  prev = curr;                    // 将前一个节点移动到当前节点  curr = nextTemp;                // 将当前节点移动到下一个节点  }  return prev;  // prev 最后会指向新的头节点  }  
}

链表合并

两个有序的链表合并,用到了哨兵dummy这个指针记录

class ListNode {  int val;  ListNode next;  ListNode(int val) {  this.val = val;  this.next = null;  }  public ListNode mergeTwoLists(ListNode l1, ListNode l2) {  // 创建一个哨兵节点,方便处理边界情况  ListNode dummy = new ListNode(0);  ListNode curr = dummy;  // 使用两个指针分别遍历两个链表  while (l1 != null && l2 != null) {  if (l1.val <= l2.val) {  curr.next = l1;  l1 = l1.next;  } else {  curr.next = l2;  l2 = l2.next;  }  curr = curr.next;  }  // 处理剩余节点(只能有一个链表还有剩余节点)  if (l1 != null) {  curr.next = l1;  } else {  curr.next = l2;  }  }
}

删除链表倒数第 n 个结点

使用快慢指针,快慢指针在解很多链表题目中都有体现

class ListNode {  int val;  ListNode next;    ListNode(int val) {  this.val = val;  this.next = null;  }  public ListNode removeNthFromEnd(ListNode head, int n) {  // 创建一个哨兵节点,简化头节点被删除的情况  ListNode dummy = new ListNode(0);  dummy.next = head;// 初始化快慢指针  ListNode fast = dummy;  ListNode slow = dummy;  // 先将快指针向前移动 n+1 步  for (int i = 0; i <= n; i++) {  fast = fast.next;  }    // 然后同时移动快慢指针,直到快指针到达链表末尾  while (fast != null) {  fast = fast.next;  slow = slow.next;  }    // 此时慢指针指向的节点的下一个节点就是要删除的节点  slow.next = slow.next.next;    // 返回头节点(注意是哨兵节点的下一个节点)  return dummy.next;  }    
}

找链表的中间结点

使用快慢指针来实现,快指针每次移动2步,而慢指针每次移动1步。当快指针到达链表末尾时,慢指针将恰好位于链表的中间。

class ListNode {  int val;  ListNode next;  ListNode(int val) {  this.val = val;  this.next = null;  }  public ListNode findMiddle(ListNode head) {  // 初始化快慢指针  ListNode slow = head;  ListNode fast = head;  // 快指针每次移动两步,慢指针每次移动一步  while (fast != null && fast.next != null) {  slow = slow.next;  // 慢指针移动一步  fast = fast.next.next;  // 快指针移动两步  }  // 当快指针到达链表末尾时,慢指针指向中间节点  return slow;  } 
}

链表中环的检测

快慢指针进行遍历,如果快慢指针不相遇说明没有环

class ListNode {  int val;  ListNode next;ListNode(int val) {  this.val = val;  this.next = null;  }  public boolean hasCycle(ListNode head) {  if (head == null || head.next == null) {  // 如果链表为空或只有一个节点,则不可能有环  return false;  }   ListNode slow = head;  ListNode fast = head;// 快慢指针开始移动,直到它们相遇或快指针到达链表末尾  while (fast != null && fast.next != null) {  slow = slow.next;          // 慢指针每次移动一步  fast = fast.next.next;     // 快指针每次移动两步  // 如果快慢指针相遇,说明链表中存在环  if (slow == fast) {  return true;  }  }// 快指针到达链表末尾,说明链表中没有环  return false;  }  
}

排序算法

常用的冒泡、选择、插入、归并、快速算法,手写很重要,写出来会发现即使是一个小的改动对于程序的消耗来说都有所差别;
关于排序的算法还可以参照:https://mp.weixin.qq.com/s/HQg3BzzQfJXcWyltsgOfCQ
在要求高效的很多基础框架代码中都是用了快速排序(递归思路)

// 冒泡排序  
void bubbleSort(int[] arr) {  int n = arr.length;  for (int i = 0; i < n - 1; i++) {  for (int j = 0; j < n - i - 1; j++) {  if (arr[j] > arr[j + 1]) {  // 交换arr[j]和arr[j + 1]  int temp = arr[j];  arr[j] = arr[j + 1];  arr[j + 1] = temp;  }  }  }  
}  // 选择排序  
void selectionSort(int[] arr) {  int n = arr.length;  for (int i = 0; i < n - 1; i++) {  int minIdx = i;  for (int j = i + 1; j < n; j++) {  if (arr[j] < arr[minIdx]) {  minIdx = j;  }  }  // 交换arr[i]和arr[minIdx]  int temp = arr[minIdx];  arr[minIdx] = arr[i];  arr[i] = temp;  }  
}  // 插入排序  
void insertionSort(int[] arr) {  int n = arr.length;  for (int i = 1; i < n; i++) {  int key = arr[i];  int j = i - 1;  // 将arr[i]插入到已排序部分arr[0..i-1]  while (j >= 0 && arr[j] > key) {  arr[j + 1] = arr[j];  j = j - 1;  }  arr[j + 1] = key;  }  
} 
// 归并排序  
void mergeSort(int[] arr, int left, int right) {  if (left < right) {  int mid = left + (right - left) / 2;  // 递归排序两个子数组  mergeSort(arr, left, mid);  mergeSort(arr, mid + 1, right);  // 合并两个已排序的子数组  merge(arr, left, mid, right);  }  
}  
void merge(int[] arr, int left, int mid, int right) {  int n1 = mid - left + 1;  int n2 = right - mid;  int[] L = new int[n1];  int[] R = new int[n2];  for (int i = 0; i < n1; i++) L[i] = arr[left + i];  for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];  int i = 0, j = 0;  int k = left;  while (i < n1 && j < n2) {  if (L[i] <= R[j]) {  arr[k] = L[i];  i++;  } else {  arr[k] = R[j];  j++;  }  k++;  }  while (i < n1) {  arr[k] = L[i];  i++;  k++;  }  while (j < n2) {  arr[k] = R[j];  j++;  k++;  }  
}    
// 快速排序  
void quickSort(int[] arr, int low, int high) {  if (low < high) {  int pi = partition(arr, low, high);  // 递归排序两个子数组  quickSort(arr, low, pi - 1);  quickSort(arr, pi + 1, high);  }  
}  
int partition(int[] arr, int low, int high) {  int pivot = arr[high];  int i = (low - 1);  for (int j = low; j < high; j++) {  if (arr[j] < pivot) {  i++;  // 交换arr[i]和arr[j]  int temp = arr[i];  arr[i] = arr[j];  arr[j] = temp;  }  }  // 交换arr[i + 1]和arr[high] (或pivot)  int temp = arr[i + 1];  arr[i + 1] = arr[high];  arr[high] = temp;  return i + 1;  
}

递归

递归是一种分治的思维,不适合人类大脑但天然是计算机的处理方式,人类大脑总是想把事情的步骤想的很清晰,12345每一步骤做什么,但是计算机不是这样的;
递归也存在堆栈溢出和重复计算的问题,专栏中也给了对应的方式,重复计算可以通过缓存来解决;

// 上楼梯问题中可以适当增加缓存来消除重复计算
public int f(int n) {if (n == 1) return 1;if (n == 2) return 2;// hasSolvedList 可以理解成一个 Map,key 是 n,value 是 f(n)if (hasSolvedList.containsKey(n)) {return hasSovledList.get(n);}int ret = f(n-1) + f(n-2);hasSovledList.put(n, ret);return ret;
}

相关文章:

  • 求职Leetcode题目(12)
  • Spring Boot技术:构建高效网上购物平台
  • 《黑神话:悟空》在全球爆火的原因是什么?
  • Ubuntu开机进入紧急模式处理
  • windows10 docker 推送本地镜像
  • SQL进阶技巧:如何获取状态一致的分组? | 最大、最小值法
  • JVM(HotSpot):字符串常量池(StringTable)
  • 在Robot Framework中Run Keyword If的用法
  • 汽车发动机控制存储芯片MR2A08A
  • Trilium Notes笔记本地化部署与简单使用指南打造个人知识库
  • 云计算Openstack Glance
  • 网络战时代的端点安全演变
  • Linux 下 C++ 操作串口并彻底释放控制权的总结
  • c#中字符串处理的技巧集合
  • socket网络编程
  • JavaScript 如何正确处理 Unicode 编码问题!
  • Angular Elements 及其运作原理
  • JS笔记四:作用域、变量(函数)提升
  • laravel with 查询列表限制条数
  • React系列之 Redux 架构模式
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • Zepto.js源码学习之二
  • 服务器之间,相同帐号,实现免密钥登录
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 我这样减少了26.5M Java内存!
  • 以太坊客户端Geth命令参数详解
  • Android开发者必备:推荐一款助力开发的开源APP
  • puppet连载22:define用法
  • 阿里云重庆大学大数据训练营落地分享
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (2022 CVPR) Unbiased Teacher v2
  • (31)对象的克隆
  • (35)远程识别(又称无人机识别)(二)
  • (poj1.2.1)1970(筛选法模拟)
  • (二)Linux——Linux常用指令
  • (二)springcloud实战之config配置中心
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)Neo4j下载安装以及初次使用
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • (转)Oracle 9i 数据库设计指引全集(1)
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .net Stream篇(六)
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .netcore 获取appsettings
  • .NET实现之(自动更新)
  • @Mapper作用
  • [] 与 [[]], -gt 与 > 的比较