算法编程
这是本人在牛客网上做的算法题总结,如有侵权请告知本人,本人将其删除,转载请说明出处;各个大厂招人的觉得我写的也可以联系我:联系方式:19937164020(同微信)
1、链表反转
描述:
输入一个链表,反转链表后,输出新链表的表头。
示例:
输入:
{1,2,3}
返回值:
{3,2,1}
答案:
public class Solution {
// 传入一个链表,返回一个链表
public ListNode ReverseList(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode last = ReverseList(head.next);
head.next.next = head;
head.next = null;
return last;
}
}
测试链表反转的方法:
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
//将数组的值赋给链表
public ListNode getList(int[] sums) {
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
for (int i = 0; i < sums.length; i++) {
curr.next = new ListNode(sums[i]);
curr = curr.next;
}
return dummyHead.next;
}
//将链表的值赋给list并打印
public static void showList(ListNode listNode) {
List list = new ArrayList();
while (listNode != null) {
list.add(listNode.val);
listNode = listNode.next;
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
public static void main(String[] args) {
int a[] = {1, 2, 3};
ListNode curr = new ListNode(0);
ListNode l1 = curr.getList(a);
showList(l1);
showList(new Solution().ReverseList(l1));
}
}
2、LUR(缓存)最少使用原则
首先补充知识: 二维数组的初始化
静态初始化:
int[][] arr2 = new int[][]{{1, 2}, {3, 4, 5}, {6, 7}};
int[][] arr3 = {{1, 2}, {3, 4, 5}, {6, 7}};
总结:二维数组的静态初始化,在定义数组时,就已经初始化;
注意:并且[][]里面的数字可以不写;
动态初始化:
int[][] arr = new int[6][1]; // [1] 这里的1并不代表以后存放arr长度的上限,只影响初始值
arr[0] = new int[]{1, 1, 1};
arr[1] = new int[]{1, 2, 2};
arr[2] = new int[]{1, 3, 2};
arr[3] = new int[]{2, 1};
arr[4] = new int[]{1, 4, 4};
arr[5] = new int[]{2, 2};
总结: arr[5] // 代表第6行
arr[5][2] // 第一个[5] 代表最后六行, [2]代表第六行的第三个数
补充知识:
map的一些常规操作:
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 2);
map.put(2, 3);
map.put(3, 4);
map.put(4, 4);
Iterator<Integer> it = map.keySet().iterator(); // 获取一个迭代器
System.out.println(map.keySet()); // 通过map获取所有的key集合
System.out.println(map.values()); // 通过map获取所有的values
System.out.println(it.next()); // it.next() 代表通过迭代器获取key的值
System.out.println(map.get(it.next())); // 通过 it.next() (key)获取 value 的值
System.out.println(map.containsKey(1)); // 是否包含 key
运行结果:
[1, 2, 3, 4]
[2, 3, 4, 4]
1
3
true
题目描述:
结果:
解释:
答案:
public int[] LRU(int[][] operators, int k) {
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>();
LinkedList<Integer> list = new LinkedList<>();
for (int[] operator : operators) { // 从operators的第一行开始遍历,一直到最后一行
int key = operator[1]; // 代表 operators 的第0行的,第一个数字,代表key
switch (operator[0]) {
case 1: // 说明 opt = 1;
int value = operator[2]; // 代表value
if (map.size() < k) {
map.put(key, value);
} else {
Iterator it = map.keySet().iterator();
map.remove(it.next());
map.put(key, value);
}
break;
case 2:
if (map.containsKey(key)) {
int val = map.get(key);
list.add(val);
map.remove(key);
map.put(key, val);
} else {
list.add(-1);
}
break;
default:
}
}
int[] res = new int[list.size()];
int i = 0;
for (Integer val : list) {
res[i++] = val;
}
return res;
}
总结:
这道题:
二维数组的第一个数:(1或者2)
1代表 set(key,value);
2代表 get(key);
switch 效率会更高一些;
这道题学到了什么:
关于二维数组:
初始化两种方式:静态初始化和动态初始化;
arr[5][2] // 第一个[5] 代表最后六行, [2]代表第六行的第三个数
arr[5] // 代表第6行
for (int[] operator : operators) {
int key = operator[1]; // 代表 operators二维数组从第0行开始遍历,一直遍历到最后一行,而且每一行取operator[1]代表每一行的第二个数字;
}
链表给数组赋值的方法:
list是一个链表,通过迭代器的方式给数组赋值;
int[] res = new int[list.size()];
int i = 0;
for (Integer val : list) {
res[i++] = val;
}
return res;
关于map的:
map.keySet(); // 获取所有的key集合
map.values(); // 获取所有的value的值
it.next(); // 通过迭代器获取第一个 key
map.containsKey(1); // map中是否包含 key
Iterator<Integer> it = map.keySet().iterator(); // 获取迭代器
这道题的延伸:
此次应该返回缓存里面的数据map的,返回数组,有就直接返回值,没有就返回-1;
3、判断链表中是否有环
判断给定的链表中是否有环,如果有环则返回true,否则返回false。
您能给出空间复杂度0(1)的解法么?
输入两部分,第一部分代表链表,第二部分代表是否有环,然后回组成head头结点传入到函数里面,-1代表无环,其他数字代表有环,这些参数解释仅仅是为了读者方便自测调试
示例:
关于这道题: 要学的知识:
第一点:链表节点的定义;
public class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
next = null;
}
}
第二点:手写链表(linkedList),提供链表的两种初始化方式,打印和链表长度,最后判断链表是否有环;
public class Solution {
private ListNode head;
private ListNode current;
// 尾插法添加节点元素, 是不会出现循环链表的;
private void addEnd(int data) {
if (head == null) {
head = new ListNode(data);
current = head;
} else {
current.next = new ListNode(data);
current = current.next;
}
}
// 打印链表
private void printList(ListNode listNode) {
if (listNode == null)
return;
current = listNode;
while (current != null) {
System.out.println("当前链表的节点:" + current.val);
current = current.next;
}
}
// 获取链表的长度
private int listLength(ListNode listNode) {
if (listNode == null)
return -1;
int length = 0;
current = listNode;
while (current != null) {
length++;
current = current.next;
}
return length;
}
// 链表的初始化一种有环的方式
private ListNode initList() {
// 链表的初始化
ListNode node1 = new ListNode(5);
ListNode node2 = new ListNode(3);
ListNode node3 = new ListNode(7);
ListNode node4 = new ListNode(2);
ListNode node5 = new ListNode(6);
// 串成链表 这样能手写有环
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node2;
return node1;
}
// 使用快慢指针 判断有环
public boolean hasCycle(ListNode head) {
ListNode p1 = head;
ListNode p2 = head;
// 一直追赶 while循环要有跳出语句 第一个跳出
while (p2 != null && p2.next != null) {
p1 = p1.next;
p2 = p2.next.next;
// 说明追赶上,return 跳出追赶
if (p1 == p2) {
return true;
}
}
return false;
}
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.hasCycle(solution.initList()));
//https://www.cnblogs.com/alimayun/p/12781501.html
}
}
4、两个栈实现队列
用两个栈实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。队列中的元素为int 类型。保证操作合法,既保证pop操作时队列内已有元素。
示例:
输入:
[“PUSH1”,“PUSH2”,“POP”,“POP”]
返回:
1,2
解析:
“PUSH1”: 代表将1插入队列尾部
“PUSH2”: 代表将2插入队列尾部
“POP”: 代表删除一个元素,先进先出=》 返回1
“POP”: 代表删除一个元素,先进先出=》返回2
示例:
此题的意义就是使用两个栈,来编写一个队列;那么队列的实现方式就可以使用两个栈来实现;
答案:
public class Solution {
private Stack<Integer> stack1 = new Stack<>();
private Stack<Integer> stack2 = new Stack<>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if (stack2.size()<=0){
while (stack1.size()!=0){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public static void main(String[] args) {
Solution solution = new Solution();
solution.push(1);
solution.push(2);
System.out.println(solution.pop());
solution.push(3);
System.out.println(solution.pop());
solution.push(4);
System.out.println(solution.pop());
System.out.println(solution.pop());
}
}
5、有数数组的二分查找
public int search(int[] nums, int target) {
int index = -1;
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] == target) {
index = mid;
high = mid - 1;
} else if (target > nums[mid]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return index;
}
6、二叉树的广度遍历
在做这道题之前,要明白什么是二叉树;
二叉树: 最多有两个节点的树;
二叉树的分类: 斜树、满二叉树、完全二叉树、平衡二叉树
斜树: 左子树,右子树;
满二叉树: 树的每个节点必须有0个或者2个子节点的二叉树。
完全二叉树: 在二叉树里面除了最下面的2层节点之外,之上的节点都必须有2个孩子节点。总结(倒数两行没有要求,倒数第三行必须是满二叉树)
平衡二叉树:它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
二叉树的遍历分为两种:
深度优先搜索:
1、前序遍历(根左右)
2、中序遍历(左根右)
3、后序遍历(左右根)
广度遍历:
只有一种策略按层级顺序遍历,遍历的顺序是从顶到底,从左到右
答案:
7、最大公约数
如果有一个自然数a能被b整除,则称a为b的倍数,b为a的约数,几个自然数共有的约数,叫做这几个自然数的公约数,公约数中最大的一个公约数,称为这几个自然数的最大公约数;
public class Solution {
public int gcd (int a, int b) {
if(a%b==0){
return b;
}
return gcd(b,a%b);
}
}
8、判断回文字符串
给定一个长度为n的字符串,请编写一个函数判断该字符串是否回文;
public boolean judge (String str) {
return str.equals(new StringBuilder(str).reverse().toString());
}
9、经典暴力题型
//x为鸡的数目,最小是0,最大为50,在一个循环中一个一个的测试,看哪一个条件能够满足题目要求
for(int x=0;x<=50;x++){
int y=50-x;
if(x*2+y*4==120)
{
System.out.println("x="+x+"y="+y);
}
}
for(int i=0;i<=2000;i++)
{
if(i%5==1 && i%7==2 && i%8==3)
{
System.out.println(i);
}
}
10、螺旋矩阵
描述:
给定一个m*n 大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中所有的元素。
输入:
[[1,2,3],[4,5,6],[7,8,9]]
返回值:
[1,2,3,6,9,8,7,4,5]
答案:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> spiralOrder(int[][] matrix) {
ArrayList<Integer> res = new ArrayList();
if (matrix.length == 0) {
return res;
}
// 定义四个指针,并且充当边界的作用
/*
matrix[2][3]
1 2 3
4 5 6
matrix.length = 2 就是 代表矩阵有2行
matrix[0].length = 3 代表 一行有3个元素
*/
int top = 0, bottom = matrix.length - 1; // 1
int left = 0, right = matrix[0].length - 1; // 2
while (top < (matrix.length + 1) / 2 && left < (matrix[0].length + 1) / 2) {
//上面 左到右
for(int i = left; i <= right; i++){
res.add(matrix[top][i]);
}
//右边 上到下
for(int i = top+1; i <= bottom; i++){
res.add(matrix[i][right]);
}
//下面 右到左
for(int i = right-1; top!=bottom && i>=left; i--){
res.add(matrix[bottom][i]);
}
//左边 下到上
for(int i = bottom-1; left!=right && i>=top+1; i--){
res.add(matrix[i][left]);
}
// 遍历一圈之后往里面靠
++top;
--bottom;
++left;
--right;
}
return res;
}
}
总结:
二维数组知识点:
matrix[2][3] 是一个二维数组;
1 2 3
4 5 6
matrix.length = 2 就是 代表矩阵有2行
matrix[0].length = 3 代表 一行有3个元素
关于二维数组:
初始化两种方式:静态初始化和动态初始化;
arr[5][2] // 第一个[5] 代表最后六行, [2]代表第六行的第三个数
arr[5] // 代表第6行
for (int[] operator : operators) {
int key = operator[1]; // 代表 operators二维数组从第0行开始遍历,一直遍历到最后一行,而且每一行取operator[1]代表每一行的第二个数字;
}
11、跳台阶
描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙上一个n 级台阶总共有多少种跳法;
public class Solution {
public int jumpFloor(int target) {
if(target==1){
return 1;
} else if(target==2){
return 2;
}
return jumpFloor(target-1)+jumpFloor(target-2);
}
}
12、合并两个有序数组
描述:
给一个整数数组A,和有序数组B,请将数组B合并到A中,变成一个有序的升序数组;
注意:
A的数组空间大小为 m+n, A和B的初始元素数目为 m和 n;
import java.util.*;
import java.util.Arrays;
public class Solution {
public void merge(int A[], int m, int B[], int n) {
for (int i = 0; i < n; i++) {
A[m + i] = B[i]; // 不会越界,因为题目说: A数组的初始元素是3个,但是他的容量是 m+n
}
Arrays.sort(A);
}
}
13、连续子数组的最大和
输入一个长度为n的整形数组array,数组中的一个或者连续多个整数组成一个子数组,求所有子数组和的最大值;
输入:
[1,-2,3,10,-4,7,2,-5]
返回值:
18
说明:
经分析可知,输入数组的子数组[3,10,-4,7,2]可以求得最大和为18
题解:
只需做两次比较即可:
第一次: sum = max( sum+array[0] 和 sum )取最大值
第二次: max=max( sum , max );
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int sum=0;
int max=array[0];
for(int i=0;i<array.length;i++){
sum=Math.max(sum+array[i],array[i]); // 将当前值 和 总和+当前值 比较取最大值
max=Math.max(max,sum); // 将总和 和 最大值比较 取最大值
}
return max;
}
}
14、买股票的最佳时期
假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天
2.如果不能获取到任何利润,请返回0
3.假设买入卖出均无手续费
题解:
1、找出最低价 也就是最适合买的时期
2、计算当天的差价,最适合卖的时期
public class Solution {
public int maxProfit (int[] prices) {
int sum=0;
int min=prices[0];
for(int i=0;i<prices.length;i++){
min=Math.min(min,prices[i]); // 找出最低价 也就是最适合买的时期
sum=Math.max(sum,prices[i]-min); // 计算当天的差价,最适合卖的时期
}
return sum;
}
}
15、链表中环的入口节点
描述:
给定 一个长度为n 的链表,若其中包含环, 请找出该链表的环的入口节点,否则返回 null.
例如:输入 {1,2},{3,4,5}时,返回3;
对应的环形链表如下图所示
输入两段,第一段是入环前的链表部分,第二段是链表的环的部分,后台会根据第二段是否为空将这两段组装成一个无环或者有环的单链表;
返回值描述:
返回链表的环的入口节点即可,我们后台程序会打印这个结点对应的节点值;若没有,则返回对应编程语言的空节点即可。
输入:
{1,2},{3,4,5}
返回值:
3
说明:
返回环形链表入口结点,我们后台程序会打印该环形链表入口结点对应的结点值,即3
// 节点类
public class ListNode {
int val; // 值域
ListNode next=null; // 指针域
ListNode(int val){
this.val = val;
}
}
import java.util.HashSet;
public class MyTestList {
// 有环的入口节点
public ListNode EntryNodeOfLoop(ListNode pHead) {
HashSet<ListNode> set = new HashSet<ListNode>();
while(pHead!=null){
if(set.contains(pHead)){
return pHead;
}
set.add(pHead);
pHead=pHead.next;
}
return null;
}
// 给链表初始化
// 链表的初始化一种有环的方式
private ListNode initList() {
// 链表的初始化
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
// 串成链表 这样能手写有环
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node3;
return node1; // 链表的首地址
}
public static void main(String[] args) {
ListNode listNode = new MyTestList().EntryNodeOfLoop(new MyTestList().initList());
System.out.println(listNode.val);
}
}
16、数组中只出现一次的数
描述: 给定一个长度为n的整形数组arr 和一个整数 k (k>1)
已知arr 只有1个数出现一次,其他数都出现k次, 返回只出现一次的数。
输入:
[5,4,1,1,5,1,5], 3
返回值:
4
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param arr int一维数组
* @param k int
* @return int
*/
public int foundOnceNumber (int[] arr, int k) {
// write code here
Arrays.sort(arr);
for (int i = 0; i < arr.length-1; i++) { // 为了返回值 剩一个 最后一个就是答案
if(arr[i]==arr[i+1]){ // 前后一样说明有重复的 则跳到 下一个没有重复的
i = i + k - 1;
}else {
return arr[i]; // 没有重复,说明 就是答案
}
}
return arr[arr.length - 1]; // 数组前面都判断完了,最后一个就是答案。
}
}
总结:
hashSet 不能存储相同的值
17、求平方根
描述:
实现函数 int sqrt(int x)
计算并返回x的平方根(向下取整)
方法一:
public class Solution {
/**
*
* @param x int整型
* @return int整型
*/
public int sqrt (int x) {
// write code here
return (int)Math.sqrt(x);
}
}
第二种:
解法二:根据平方数的性质——连续n个奇数相加的结果一定是平方数。
如:9=1+3+5;
16=1+3+5+7;
所以,不断的进行奇数相加,并判断x大小即可。代码如下:
public class Solution {
public int sqrt(int x) {
int i = 1;
int res = 0;
while (x >= 0) {
x -= i;
res++;
i += 2;
}
return res - 1;
}
}
18、两数之和
描述:
给出一个整形数组nunbers和一个目标值target ,请在数组中找出两个相加起来等于目标值的树的下标,返回的下标按照升序排序
实例:
输入:
[3,2,4],6
返回值:
[2,3]
说明:
因为 2+4=6 ,而 2的下标为2 , 4的下标为3 ,又因为 下标2 < 下标3 ,所以输出[2,3]
答案:
public int[] twoSum (int[] numbers, int target) {
// write code here
// write code here
int[] res = new int[2];
for (int i = 0; i < numbers.length; i++) {
int second = target - numbers[i];
for (int j = i + 1; j < numbers.length; j++) {
if (second == numbers[j]) {
res[0] = i+1;
res[1] = j+1;
}
}
}
Arrays.sort(res);
return res;
}
19、最长公共前缀
描述:
给你一个大小为n的字符串 strs,其中包含n个字符串,编写一个函数来查找字符串数组中的最长公共前缀,返回这个公共前缀。
实例
输入:
["abca","abc","abca","abc","abcc"]
返回值:
"abc"
解法一:纵向扫描
- 将字符串看做一个二维空间,每一次从第一列开始。
- 确定所有字符串第一列字符。
- 逐层扫描后面每一列,遇到不同字符停止扫描。
答案:
public class Solution {
public String longestCommonPrefix (String[] strs) {
// write code here
// write code here
if (strs.length == 0 || strs == null) {
return "";
}
int rows = strs.length; // 大数组的长度
int cols = strs[0].length(); // 第一个字符产的长度
for (int i = 0; i < cols; i++) { //针对第一个字符串来说
char firstChar = strs[0].charAt(i); // 拿 第一个 字符串,来比较
for (int j = 1; j < rows; j++) {
if (strs[j].length() == i || strs[j].charAt(i) != firstChar) {
return strs[0].substring(0, i);
}
}
}
return strs[0];
}
}
20、合并两个排序的链表
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
输入:
{1,3,5},{2,4,6}
返回值:
{1,2,3,4,5,6}
解题思路:
先在两个头结点选择一个较小的作为新链表的头结点,
被选的节点指向下一个节点,作为新的链表(比如 ListNode list1)
新头结点的下一个节点就是再次比较两个排序链表的头节点的大小,使用递归。
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null) {
return list2;
}
else if(list2 == null) {
return list1;
}
ListNode newHead = null;
if(list1.val < list2.val) {
newHead = list1;
list1 = list1.next;
newHead.next = Merge(list1, list2);
}
else {
newHead = list2;
list2 = list2.next;
newHead.next = Merge(list1, list2);
}
return newHead;
}
public static void main(String[] args) {
ListNode listNode1 = new ListNode(1); // 前提必须是 两个有序的链表
ListNode listNode2 = new ListNode(3);
ListNode listNode3 = new ListNode(5);
listNode1.next = listNode2;
listNode2.next = listNode3;
ListNode listNode4 = new ListNode(2);
ListNode listNode5 = new ListNode(4);
ListNode listNode6 = new ListNode(6);
listNode4.next = listNode5;
listNode5.next = listNode6;
ListNode merge = new Solution().Merge(listNode1, listNode4);
ListNode current;
if (merge == null)
return;
current = merge;
while (current != null) {
System.out.println("当前链表的节点:" + current.val);
current = current.next;
}
}
}
21、单链表排序
描述:
给定一个节点为n 的无序单链表,对其按升序排序;
思想: 将链表里面的数放到数组中;
对数组排序;
再将数组的值付给 新的链表;
public class Solution {
public ListNode sortInList(ListNode head) {
// write code here
List<Integer> list = new ArrayList<Integer>(); //给数组赋值
while (head!= null) {
list.add(head.val);
head = head.next;
}
Collections.sort(list, new Comparator<Integer>() { // 给数组排序
public int compare(Integer o1, Integer o2) {
if (o1>o2){
return 1;
}else {
return -1;
}
}
});
return arrayToListNode(list); // 将数组转化成 链表
}
//数组转换成链表
public ListNode arrayToListNode(List<Integer> s) {
ListNode root = new ListNode(s.get(0));//生成链表的根节点,并将数组的第一个元素的值赋给链表的根节点
ListNode other = root;//生成另一个节点,并让other指向root节点,other在此作为一个临时变量,相当于指针
for (int i = 1; i < s.size(); i++) {//由于已给root赋值,所以i从1开始
ListNode temp = new ListNode(s.get(i));//每循环一次生成一个新的节点,并给当前节点赋值
other.next = temp;//将other的下一个节点指向生成的新的节点
other = temp;//将other指向最后一个节点(other的下一个节点) other=other.getNext();
}
return root;
}
public static void main(String[] args) {
ListNode listNode1 = new ListNode(5);
ListNode listNode2 = new ListNode(3);
ListNode listNode3 = new ListNode(1);
listNode1.next = listNode2;
listNode2.next = listNode3;
new Solution().printList(new Solution().sortInList(listNode1));
}
}
22、判断一个链表是否为回文结构
描述:
给定一个链表,请判断链表是否为回文结构
思路:
1、将链表存放到数组里面;
2、首位指针判断数组是否回文;
public class Solution {
public boolean isPail(ListNode head) {
List<Integer> a = new ArrayList<Integer>();
for (; head != null; head = head.next) {
a.add(head.val);
}
for (int i = 0, j = a.size() - 1; i < j; i++, j--) {
if (!a.get(i).equals(a.get(j))) {
return false;
}
}
return true;
}
}
23、有效括号
给出一个仅包含字符’(’,’)’,’{’,’}’,’[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列
括号必须以正确的顺序关闭,"()“和”()[]{}"都是合法的括号序列,但"(]“和”([)]"不合法。
思路:
1、每次遇到 ( 、{、[ 则入栈
2、遇到 )} ] 时,如果栈为空,或者栈顶元素不为匹配元素时,则括号序列不合法;
3、当栈为非空,且栈顶元素为匹配元素,栈顶元素出栈
4、循环匹配,判断占是否为空;
public class Solution {
public boolean isValid(String s)
{
if(s.length() %2 != 0) //先行判断剔除
return false;
Stack<Character> stack = new Stack<Character>(); //创建 stack 对象
for(int i=0; i<s.length();i++)
{
char c = s.charAt(i);
if(c == '(' || c == '[' || c == '{') // 入栈
stack.push(c);
else
{
if(stack.isEmpty()) //针对首先出现的右侧符号进行判断
return false;
char top = stack.pop(); // 出栈 过来一个 ) top= (
if(c == ')' && top != '(') //对应匹配
return false;
if(c == ']' && top != '[')
return false;
if(c == '}' && top != '{')
return false;
}
}
return stack.isEmpty(); //检查栈是否为空
}
}
24、调整数组顺序,使奇数位于偶数的前面
描述:
输入一个长度为n 整数数组,数组里面含有相同的元素,实现一个函数来调整该数组中数字的顺序,使所有奇数位于数组的前面部分,所有偶数位于数组后面的部分,保证相对位置不变:
思想:
创建两个临时数组,分别存放奇数和偶数,然后两个临时数组再将数据放到原始数组里面;
输入:
[2,4,6,5,7]
返回值:
[5,7,2,4,6]
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] reOrderArray (int[] array) {
// write code here
List<Integer> list1=new ArrayList<Integer>(); // 存奇数
List<Integer> list2=new ArrayList<Integer>(); // 存偶数
for(int i=0;i<array.length;i++){
if(array[i]%2!=0){
list1.add(array[i]);
}else{
list2.add(array[i]);
}
}
for(int i=0;i<list1.size();i++){
array[i]=list1.get(i);
}
for(int i=list1.size(),j=0;i<list1.size()+list2.size()&&j<list2.size();i++,j++){
array[i]=list2.get(j);
}
return array;
}
}
25、判断是不是平衡二叉树
描述:
输入一棵节点树为n二叉树,判断该二叉树是否为平衡二叉树。
这样创建一棵树:
树的节点类:
public class TreeNode {
int data;
TreeNode leftChild;
TreeNode rightChild;
public TreeNode() {
}
// 提供构造函数初始化
public TreeNode(int data) {
this.data = data;
}
}
平衡二叉树的初始化,以及提供判断是不是平衡二叉树的方法:
public class TestTree {
// 平衡二叉树的初始化
public TreeNode getTree() {
TreeNode treeNode1 = new TreeNode(1);
TreeNode treeNode2 = new TreeNode(2);
TreeNode treeNode3 = new TreeNode(3);
TreeNode treeNode4 = new TreeNode(4);
TreeNode treeNode5 = new TreeNode(5);
TreeNode treeNode6 = new TreeNode(6);
TreeNode treeNode7 = new TreeNode(7);
treeNode1.leftChild = treeNode2;
treeNode1.rightChild = treeNode3;
treeNode2.leftChild = treeNode4;
treeNode2.rightChild = treeNode5;
treeNode3.leftChild = treeNode6;
treeNode3.rightChild = treeNode7;
return treeNode1;
}
// 根据平衡二叉树的特点,判断是不是平衡二叉树
public boolean IsBalanced_Solution(TreeNode root) {
if (root == null) {
return true;
}
return Math.abs(maxDepth(root.leftChild) - maxDepth(root.rightChild)) <= 1 &&
IsBalanced_Solution(root.leftChild) && IsBalanced_Solution(root.rightChild);
}
private int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(maxDepth(root.leftChild), maxDepth(root.rightChild));
}
public static void main(String[] args) {
// 测试
System.out.println(new TestTree().IsBalanced_Solution(new TestTree().getTree()));
}
}
26、对称二叉树
描述: 给定一棵二叉树,判断其自身的镜像是否对称;
下面这一棵树就是对称二叉树:
下面则不是:
示例:
输入:
{1,2,2,3,4,4,3}
返回值:
true
思路:
第一: 首先根节点以及左右子树,若根节点为 null,直接返回 true;
第二: 左子树的左子树 和 右子树的右子树相同 并且 左子树的右子树和 左子树的左子树相同即可
采用递归;
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if (pRoot==null){
return true;
}
return comRoot(pRoot.left, pRoot.right);
}
private boolean comRoot(TreeNode left, TreeNode right) {
if (left==null){ // 以上三个条件都是 return 出栈的条件,递归必须要写出栈的条件;
return right == null;
}
if (right==null){ // 走到这一步,则说明 左子树不为 null
return false;
}
if (left.val!=right.val){
return false;
}
return comRoot(left.left, right.right) && comRoot(left.right, right.left);
}
}
27、二叉树的镜像
描述:
操作指定的二叉树,将其变换成源二叉树的镜像。
例子:
镜像二叉树:
思路:
第一步: 先处理根节点,根节点为 null,直接返回
第二步: 交换左右节点
public TreeNode Mirror (TreeNode pRoot) {
// write code here
if(pRoot==null){
return pRoot;
}
if(pRoot.left!=null || pRoot.right!=null){
TreeNode temp=pRoot.left;
pRoot.left=pRoot.right;
pRoot.right=temp; //交换根节点的左右子树,以上是处理书的逻辑
Mirror(pRoot.left);
Mirror(pRoot.right);
}
return pRoot;
}
28、只出现一次的字符
描述:
在一个 字符串中找到一个只出现一次的字符,返回发的位置,如果没有 返回 -1;
输入:
"google"
返回值:
4
思路一:
由于字符只出现一次,所以 该字符 第一次出现的位置 和 他最后出现的一次位置 是相同的,遍历字符串,如果第一次出现的位置等于 最后一次出现的位置,返回下标:
public class Solution {
public int FirstNotRepeatingChar(String str) {
char[] splitChat = str.toCharArray();
for (int i = 0; i < splitChat.length; i++) {
if (str.indexOf(splitChat[i]) == str.lastIndexOf(splitChat[i])) {
return i;
}
}
return -1;
}
}
总结: 字符判断 用 == 因为是 八大类型;
思路二:
根据hashMap的特性,只允许出现一次 key, key不能相同,如果包含map.containsKey(str.charAt(i)) , value的值加一,根据这个特性,统计出单个字符串出现的次数,然后 遍历 map的value的值,如果等于 1,说明第一次出现一次,直接返回;
import java.util.HashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
HashMap<Character, Integer> map = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
if (map.containsKey(str.charAt(i))) { // 如果有 key 就 value加一,没有的话,放进去
int time = map.get(str.charAt(i)); // 获取value
map.put(str.charAt(i), ++time);
} else {
map.put(str.charAt(i), 1);
}
}
for (int i = 0; i < str.length(); i++) {
if (map.get(str.charAt(i)) == 1) {
return i;
}
}
return -1;
}
}
使用hashMap统计 string 里面 字符出现的次数
public HashMap countChar(String str){
HashMap<Character, Integer> map = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
if (map.containsKey(str.charAt(i))) { // 如果有 key 就 value加一,没有的话,放进去
int time = map.get(str.charAt(i)); // 获取value
map.put(str.charAt(i), ++time);
} else {
map.put(str.charAt(i), 1);
}
}
return map;
}
// 解释: map里面的 key 代表字符,value 代表出现的次数
29、两个链表的第一个公共节点
描述:
输入两个无环的单向链表,找到他们的第一个公共结点,(说明: 公共节点的头结点都是一样的),如果没有公共结点,则返回null;
说明:
输入:
{1,2,3},{4,5},{6,7}
返回值:
{6,7}
说明:
第一个参数{1,2,3}代表是第一个链表非公共部分,第二个参数{4,5}代表是第二个链表非公共部分,最后的{6,7}表示的是2个链表的公共部分
这3个参数最后在后台会组装成为2个两个无环的单链表,且是有公共节点的
思路一:
暴力反射:拿链表1的第一个节点和链表2下所有节点遍历,然后拿链表1的第二个节点和链表2下的节点比较,以此类推:
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
ListNode firstNode = pHead2;
for (; pHead1 != null; pHead1 = pHead1.next) {
if (pHead2 == null) {
pHead2 = firstNode;
}
for (; pHead2 != null; pHead2 = pHead2.next) {
if (pHead1 == pHead2) {
return pHead1;
}
}
}
return null;
}
思路二:
一种解决方式就是先把第一个链表的节点全部存放到集合set中,然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交,直接返回null即可
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
HashSet hashset= new HashSet();
while(pHead1!=null){
hashset.add(pHead1);
pHead1=pHead1.next;
}
while(pHead2!=null){
if(hashset.contains(pHead2)){
return pHead2;
}
pHead2=pHead2.next;
}
return null;
}
思路三:
走A+B 路线。总长相等,步长相等,肯定会相遇‘
import java.util.*;
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode n1 = pHead1;
ListNode n2 = pHead2;
while (n1 != n2) {
if (n1==null) {
n1 = pHead2;
}else{
n1 = n1.next;
}
if (n2==null) {
n2 = pHead1;
}else{
n2 = n2.next;
}
} // while不再循环说明 n1和n2 相等
return n1;
}
}
30、求二叉树的深度
描述:
求二叉树的最大深度;
思路:
1、如果一棵树只有一个节点,它的深度为1.
2、如果根节点只有左子树而没有右子树,那么树的深度应该是左子树的深度加1;同样如果根节点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1.
3、如果既有右子树又有左子树,那么树的深度就是左右子树的较大值再加1.
public int maxDepth (TreeNode root) {
// write code here
int left=0;
int right=0;
if (root != null) {
left = maxDepth(root.left) ;
right =maxDepth(root.right) ;
return left > right ? left + 1 : right + 1;
}
return 0;
}
这是本人在牛客网上做的算法题总结,如有侵权请告知本人,本人将其删除,转载请告知出处;各个大厂招人的也可以加我:联系方式:19937164020(同微信)