剑指offer全集系列Java版本(2)

简介: 剑指offer全集系列Java版本(2)


反转链表

解法1: 使用堆栈

       思路如下, 首先创建一个堆栈, 然后依次遍历链表, 将链表的结点依次存入堆栈, 然后新建一个链表然后依次弹出结点, 让后将此弹出的结点, 尾插到新链表的尾部

时间复杂度: O(n)

空间复杂度: O(n)

import java.util.*;
 
/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
 
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    public ListNode ReverseList (ListNode head) {
        if (head == null) {
            return null;
        }
        // write code here
        Stack<Integer> stack = new Stack<>();
        ListNode newHead = new ListNode(0);
        ListNode result = newHead;
        while(head != null) {
            stack.push(head.val);
            head = head.next;
        }
        while(!stack.isEmpty()) {
            newHead.val = stack.pop();
            if (stack.isEmpty()) {
                break;
            }
            ListNode tem = new ListNode(0);
            newHead.next = tem;
            newHead = newHead.next;
        }
        return result;
 
    }
}

解法2: 依次遍历

       使用多指针的方法, 依次将结点的next赋值为他的前一个结点, 然后往后遍历.

时间复杂度: O(n)

空间复杂度: O(1)

import java.util.*;
 
/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
 
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    public ListNode ReverseList (ListNode head) {
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            return head;
        }
        // write code here
        ListNode l = head;
        
        ListNode center = head.next;
        ListNode r = center.next;
 
        l.next = null;
        while (center != null) {
            center.next = l;
            l = center;
            center = r;
            if (r == null) {
                return l;
            }
            r = r.next;
        }
        return null;
 
 
    }
}

解法3: 递归

       使用递归的方法来遍历这个集合, 这种方法类似于使用堆栈的方法 , 虽然代码少了很多, 但是代码的可读性缺不高, 非常锻炼玩家的思维能力.

import java.util.*;
 
/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
 
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    public ListNode ReverseList (ListNode head) {
        // write code here
        if (head == null || head.next == null) {
            return head;
        }
        ListNode next = head.next;
        ListNode reverse  = ReverseList(next);
        next.next = head;
        head.next = null;
        return reverse;
    }
}

       需要注意的是, 如果调用过多的递归, 会导致函数调用栈溢出的情况.

替换空格

       题目中给出的字符串为一个StringBuffer字符串, 他是线程安全的字符串, 同时也可以对其进行修改的字符串(我们知道java中String类是不能修改的)

       在做题之前 , 我们首先应该去了解String类和StringBuffer, 和StringBuilder中提供了哪些方法供我们使用, 我将其整理在了后面的附录中, 可以点击目录查看.

我们有很多种实现的方法, 例如:

解法1: 新建一个StringBuffer字符串

       新建一个StringBuffer对象, 以此扫描str, 如果不是空格, 就直接追加字符到新的字符串中, 如果是空格, 就将%20追加到新的字符串中, 然后返回他的toString方法的返回值就行.

import java.util.*;
public class Solution {
    public String replaceSpace(StringBuffer str) {
      StringBuffer strb = new StringBuffer();
        for(int i = 0; i < str.length(); i++) {
            char tem = str.charAt(i);
            if (tem == ' ') {
                strb.append("%20");
            } else {
                strb.append(tem);
            }
        }
        return strb.toString();
    }
}

 解法2: delete(deleteCharAt) + insert

       直接遍历str, 如果是空格就先将其删除然后, 再插入%20

import java.util.*;
public class Solution {
    public String replaceSpace(StringBuffer str) {
      for (int i = 0; i < str.length(); i++) {
            char tem = str.charAt(i);
            if (tem == ' ') {
                str.deleteCharAt(i);
                str.insert(i,"%20");
            }
        }
        return str.toString();
    }
}


二叉树

       树是一种逻辑结构,同时也是一种分层结构,

其定义结构如下:

  • 有且仅有一个特定的点可以做为根节点
  • 树的根节点是没有前驱的
  • 树的所有结点可以有0个或者是多个后继
  • 特殊的,我们将每个父亲最多只有两个孩子的树叫做二叉树

二叉树有什么特点??

二叉树的结构代码定义:

#include<stdio.h>
#include<malloc.h>
 
typedef char ElementType;
typedef struct BNode {
  ElementType data;
  struct BNode* left;
  struct BNode* right;
}BNode,*BTree;

辅助队列:

typedef struct tag {
  BiTree p;
  struct tag* next;
}tag_t,*ptag_t;

建树的过程:

#define _CRT_SECURE_NO_WARNINGS 1
 
 
#include<stdio.h>
#include<malloc.h>
 
typedef char ElementType;
typedef struct BNode {
  ElementType data;
  struct BNode* left;
  struct BNode* right;
}BNode,* BiTree;
 
typedef struct tag {
  BiTree p;
  struct tag* next;
}tag_t,*ptag_t;
 
int main() {
  BiTree pnew;   // 树的新的结点
  BiTree tree = NULL; // 树根
  ptag_t phead = NULL, ptail = NULL, listpnew = NULL, pcur = NULL;
  char c;
  while (scanf("%c", &c)) {
    if (c == '\n') {
      break;
    }
    pnew = (BiTree)calloc(1, sizeof(BNode));
    pnew->data = c;
    listpnew = (ptag_t)calloc(1, sizeof(tag_t));
    listpnew->p = pnew;
 
    if (NULL == tree) {
      tree = pnew;
      phead = listpnew;
      ptail = listpnew;
      pcur = listpnew;
    }
    else {
      ptail->next = listpnew;
      ptail = listpnew;
      if (pcur->p->left == NULL) {
        pcur->p->left = pnew;
      }
      else if (pcur->p->right == NULL ){
        pcur->p->right = pnew;
        pcur = pcur->next;
      }
 
    }
  }
 
  return 0;
}

       树是一种简单的数据结构, 面试中提到的树一般都是二叉树, 也就是一种特殊的树结构, 根节点没有父结点. ,每个结点有一到两个子节点, 树存在着好几种遍历方式, 一般有:

  • 前序遍历: 先访问根节点, 再访问左子节点, 然后再访问右子节点
  • 中序遍历: 先访问左子节点, 然后访问根节点, 随后访问右子节点
  • 后序遍历: 先访问左子节点, 然后访问右子节点, 最后访问根节点

前中后序遍历的实现(递归实现) :

前序遍历:

       以上这三种遍历方式的六种实现应该了如指掌

       接下来看这三个遍历的例子:

void preOrder(BiTree root) {
  if (root == NULL) {
    return;
  }
  printf("%c", root->data);
  preOrder(root->left);
  preOrder(root->right);
}
 
void inOrder(BiTree root) {
  if (root == NULL) {
    return;
  }
  inOrder(root->left);
  printf("%c", root->data);
  inOrder(root->right);
}
 
void lastOrder(BiTree root) {
  if (root == NULL) {
    return;
  }
  lastOrder(root->left);
  lastOrder(root->right);
  printf("%c", root->data);
}

  • 宽度优先遍历: 先访问第一层的结点, 在访问第二层的结点, 每一层就时从左到右以此访问.

广度优先遍历的代码:

广度优先遍历需要借助一个辅助队列, 如下:

void levelOrder(BiTree root) {
  SqQueue queue; // 辅助队列
  InitQueue(queue); // 初始化一个队列.
  EnQueue(queue, root);  // 将根节点首先放入队列
  while (!isEmpty(queue)) {
    BiTree tem;
    DeQueue(queue,tem);  // 出队一个元素.
    printf("%c", tem->data); // 
    if (tem->left != NULL) {  // 如果左孩子不为空, 就将左孩子入队
      EnQueue(queue, tem->left);
    }
    if (tem->right != NULL) { // 如果右孩子不为空, 就将右孩子入队
      EnQueue(queue, tem->right);
    }
  }
}

带权路径之和:

带权路径之和为每个叶子结点的深度(路径长度)和其权值的乘积..

#define _CRT_SECURE_NO_WARNINGS 1
 
 
#include<stdio.h>
#include<malloc.h>
 
typedef struct BNode {
  char data;
  struct BNode* left;
  struct BNode* right;
}BNode, * BiTree;
 
 
typedef struct tag {
  BiTree p;
  struct tag* next;
}tag_t, * ptag_t;
 
 
int wpl = 0;
int wpl_preOrder(BiTree root, int deep) {
  if (root == NULL) {
    return;
  }
  if (root->left == NULL && root->right == NULL) {
    wpl += deep * (root->data);
  }
  printf("%c", root->data);
  wpl_preOrder(root->left, deep + 1);
  wpl_preOrder(root->right, deep +1);
  return wpl;
}
 
int WPL(BiTree root) {
  wpl = 0;
  return wpl_preOrder(root, 0);
}

链表的中间结点

题目链接:

实例

       通过例子可以知道, 加入链表有技术个数, 那么中间结点就为中间那个数, 如果为偶数个, 那么中间结点就有两个, 例如{1,2,3,4}, 中间结点为2和3, 但是我们认为中间结点为3, 于是返回3的地址.

       思路:  使用快慢指针 定义连个指针, 他们刚开始都指向头结点, 分别为slow指针和fast指针, 定义: slow指针每次只走一个结点, fast指针每次走两个结点.

经过第一次slow走一步, fast走两步:

然后再走一次, 结果如下:

       此时, fast指向NULL, slow刚好指向了中间这个结点

假设现在有奇数个结点, 如下:

       现在slow走一步, fast走两步

第一次:

第二次:

       此时, fast的next为空时, slow指向中间结点

总结, 上面两种情况, 无论是奇数个结点还是偶数个结点, 只要fast的next为NULL或者fast本身为NULL的时候, slow就会指向中间结点.

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param head ListNode类 
 * @return ListNode类
 */
struct ListNode* middleNode(struct ListNode* head ) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

附录

StringBuffer类中常用的方法

Modifier and Type Method and Description
StringBuffer append(boolean b)

boolean参数的字符串表示附加到序列中。

StringBuffer append(char c)

char参数的字符串表示附加到此序列。

StringBuffer append(char[] str)

char数组参数的字符串表示附加到此序列。

StringBuffer append(char[] str, int offset, int len)

char数组参数的子阵列的字符串表示附加到此序列。

StringBuffer append(CharSequence s)

追加指定的 CharSequence到这个序列。

StringBuffer append(CharSequence s, int start, int end)

追加指定的序列 CharSequence到这个序列。

StringBuffer append(double d)

double参数的字符串表示附加到此序列。

StringBuffer append(float f)

float参数的字符串表示附加到此序列。

StringBuffer append(int i)

int参数的字符串表示附加到此序列。

StringBuffer append(long lng)

long参数的字符串表示附加到此序列。

StringBuffer append(Object obj)

追加 Object参数的字符串表示。

StringBuffer append(String str)

将指定的字符串附加到此字符序列。

StringBuffer append(StringBuffer sb)

将指定 StringBuffer这个序列。

StringBuffer appendCodePoint(int codePoint)

codePoint参数的字符串表示法附加到此序列。

int capacity()

返回当前容量。

char charAt(int index)

返回 char在指定索引在这个序列值。

int codePointAt(int index)

返回指定索引处的字符(Unicode代码点)。

int codePointBefore(int index)

返回指定索引之前的字符(Unicode代码点)。

int codePointCount(int beginIndex, int endIndex)

返回此序列指定文本范围内的Unicode代码点数。

StringBuffer delete(int start, int end)

删除此序列的子字符串中的字符。

StringBuffer deleteCharAt(int index)

删除 char在这个序列中的指定位置。

void ensureCapacity(int minimumCapacity)

确保容量至少等于规定的最小值。

void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

字符从该序列复制到目标字符数组 dst

int indexOf(String str)

返回指定子字符串第一次出现的字符串内的索引。

int indexOf(String str, int fromIndex)

返回指定子串的第一次出现的字符串中的索引,从指定的索引开始。

StringBuffer insert(int offset, boolean b)

在此序列中插入 boolean参数的字符串表示形式。

StringBuffer insert(int offset, char c)

在此序列中插入 char参数的字符串表示形式。

StringBuffer insert(int offset, char[] str)

在此序列中插入 char数组参数的字符串表示形式。

StringBuffer insert(int index, char[] str, int offset, int len)

在此序列中插入 str数组参数的子阵列的字符串表示形式。

StringBuffer insert(int dstOffset, CharSequence s)

将指定的 CharSequence这个序列。

StringBuffer insert(int dstOffset, CharSequence s, int start, int end)

将指定的子序列 CharSequence这个序列。

StringBuffer insert(int offset, double d)

在此序列中插入 double参数的字符串表示形式。

StringBuffer insert(int offset, float f)

在此序列中插入 float参数的字符串表示形式。

StringBuffer insert(int offset, int i)

将第二个 int参数的字符串表示插入到此序列中。

StringBuffer insert(int offset, long l)

在此序列中插入 long参数的字符串表示形式。

StringBuffer insert(int offset, Object obj)

Object参数的字符串表示插入到此字符序列中。

StringBuffer insert(int offset, String str)

将字符串插入到此字符序列中。

int lastIndexOf(String str)

返回指定子字符串最右边出现的字符串内的索引。

int lastIndexOf(String str, int fromIndex)

返回指定子字符串最后一次出现的字符串中的索引。

int length()

返回长度(字符数)。

int offsetByCodePoints(int index, int codePointOffset)

返回此序列中与 indexcodePointOffset代码点偏移的索引。

StringBuffer replace(int start, int end, String str)

用指定的String中的字符替换此序列的子字符串中的 String

StringBuffer reverse()

导致该字符序列被序列的相反代替。

void setCharAt(int index, char ch)

指定索引处的字符设置为 ch

void setLength(int newLength)

设置字符序列的长度。

CharSequence subSequence(int start, int end)

返回一个新的字符序列,该序列是该序列的子序列。

String substring(int start)

返回一个新的 String ,其中包含此字符序列中当前包含的字符的子序列。

String substring(int start, int end)

返回一个新的 String ,其中包含此序列中当前包含的字符的子序列。

String toString()

返回表示此顺序中的数据的字符串。

void trimToSize()

尝试减少用于字符序列的存储。



目录
相关文章
|
9月前
|
安全 架构师 Java
Java LTS版本进化秀:从8到21的欢乐升级之旅
困惑于Java版本选择?轻松幽默地穿越Java LTS版本时光隧道,掌握从Java 8到21的关键特性。通过一家初创公司的系统升级故事,直观了解每个版本如何解决代码冗余、性能瓶颈等开发痛点,助你在技术选型中做出明智决策。
511 7
|
9月前
|
jenkins Java Shell
Java、Python、C++支持jenkins和SonarQube(全集)
Jenkins 是一个开源的持续集成(CI)和持续交付(CD)工具,用于自动化构建、测试和部署软件项目。它基于 Java 开发,支持跨平台运行,并拥有丰富的插件生态系统,可以灵活地扩展功能
778 1
|
10月前
|
安全 Java 开发者
Java集合框架:详解Deque接口的栈操作方法全集
理解和掌握这些方法对于实现像浏览器后退功能这样的栈操作来说至关重要,它们能够帮助开发者编写既高效又稳定的应用程序。此外,在多线程环境中想保证线程安全,可以考虑使用ConcurrentLinkedDeque,它是Deque的线程安全版本,尽管它并未直接实现栈操作的方法,但是Deque的接口方法可以相对应地使用。
504 12
|
10月前
|
Cloud Native Java API
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
1672 0
|
11月前
|
安全 Java API
Java 17 及以上版本核心特性在现代开发实践中的深度应用与高效实践方法 Java 开发实践
本项目以“学生成绩管理系统”为例,深入实践Java 17+核心特性与现代开发技术。采用Spring Boot 3.1、WebFlux、R2DBC等构建响应式应用,结合Record类、模式匹配、Stream优化等新特性提升代码质量。涵盖容器化部署(Docker)、自动化测试、性能优化及安全加固,全面展示Java最新技术在实际项目中的应用,助力开发者掌握现代化Java开发方法。
469 1
|
JavaScript Java 关系型数据库
家政系统源码,java版本
这是一款基于SpringBoot后端框架、MySQL数据库及Uniapp移动端开发的家政预约上门服务系统。
414 6
家政系统源码,java版本
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
864 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
Java Linux Windows
如何查看已安装的 Java 版本
要查看已安装的 Java 版本,打开命令提示符或终端,输入 `java -version`,回车后即可显示当前系统中 Java 的版本信息。
5603 1
|
Ubuntu Java Linux
如何检查 Java 版本是否兼容
要检查Java版本是否兼容,可在命令行输入“java -version”查看当前安装的Java版本,然后对比目标应用所需的Java版本,确保其满足要求。
1204 1
|
Java Maven Spring
查看springboot版本支持最高的java版本
截至最近更新,Spring Boot 3.0及以上版本支持的最高Java版本为Java 17。鉴于技术的不断演进,建议直接参考Spring Boot的官方文档获取最准确的支持信息,因为这些版本兼容性可能会随着新版本的发布而有所变化。选择与你的Spring Boot版本相匹配的Java版本,可以确保充分利用框架特性,同时保证项目的稳定性和前瞻性。
1709 0