前端面试及回答(字节跳动)(三)

目录

· 移动端的适配方案 

· get请求与post请求的区别

· this 指向相关

· 多仓库管理代码的优势

· ts 相对于 js 的优势 

· JS和TS的区别

· 说出几种vue常见指令

· v-if 和 v-show 有什么区别

· HTTP响应码 

· 如何让CSS只在当前组件中起作用 

· 单页应用优缺点

· 浏览器的存储方式

· cookie 的有效作用域

· webpack 配置

· react 声明周期

· Vue 和 React 的区别(使用和原理上都说一下)

· c/c++和 js 区别 

· 进程间通信

· HTTP1.0和HTTP2.0的区别、HTTP和HTTPS的区别

· 为什么说组合优于继承 

· CSS 格式化上下文

· 清除浮动的四种方法

· Vue双向绑定

· 实现三列布局的四种方法

· React 虚拟DOM

· React中的key是什么

· 虚拟DOM和真实DOM的区别

· React组件之间的8种通讯方式

· 手写JS观察者模式

· 代码题(查找数组中某个数的下标)(二分查找/折半查找)

· 代码题(三数之和)(双指针法)

· 代码题(任务队列)

· 代码题(找出n个元素数组中重复次数最多的数)  

· 代码题(字符数组全排列) 

· 代码题(顺时针打印二维数组)


· 移动端的适配方案 

   flex + rem + flexiable.js + less

  • 我们移动端采取 flex 布局
  • rem单位: 做移动端适配的

              rem相对单位,跟html文字大小有关系

  • 媒体查询: 检测屏幕的视口宽度
  • flexiable.js :可以根据屏幕的宽度自动修改html文字大小
  • less: less让我们的css具有了计算能力

              less 可以让我们很方便的 把 px 转换为 rem

· get请求与post请求的区别

  • 定义
  1. get 和 post请求是http协议中的两种请求方式。
  2. get一般用来获取服务器的信息的,post一般是用来更新信息。
  • 区别
  1. get请求一般用来请求获取数据,post请求一般作为发送数据到后台,传递数据,创建数据
  2. get请求也可以传参到后台,但是传递的参数则显示在地址栏,安全性低,且参数的长度也有限制(2048字符);post请求则是将传递的参数放在request body中,不会在地址栏显示,安全性比get请求高,参数没有长度限制
  3. get请求刷新浏览器或者回退没有影响,post请求则会重新请求一遍
  4. get请求可以被缓存,也会保留在浏览器的历史记录中,post请求不会被缓存,也不会保留在浏览器的历史记录中
  5. get请求通常是通过url地址请求,post常见的则是form表单请求
  6. get产生一个tcp数据包,post产生两个tcp数据包
  7. get产生的URL地址可以被Bookmark,而post不可以
  8. get请求会被浏览器主动cache,而post不会,除非手动设置
  9. 对参数的数据类型,get只接受ASCII字符,而post没有限制。
  10. get比post更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

· this 指向相关

  • this指向不同是由函数调用方式决定的
  1. 普通函数的调用,this指向window
  2. 构造函数的调用,this指向实例对象
  3. 对象方法的调用,this指向该对象方法所属的对象
  4. 定时器函数的调用,this指向window
  5. 通过事件绑定方法的调用,this指向绑定事件的对象
  6. 箭头函数的调用,this指向父级对象
  7. 匿名函数的调用,this指向全局变量window
  • 更改this指向的三个方法

  1. call() 方法-----把普通函数的this指向window的,更改this指向

  2. apply() 方法-----apply() 方法与call()很类似,它们的不同之处在于提供参数的方式,apply()使用参数数组,而不是参数列表

  3. bind()方法—bind()方法创建的是一个新函数(也称为绑定函数),它和被调用函数有相同的函数体。当目标函数被调用时,this的值就会绑定到bind()的第一个参数上面

  4. 存储this指向到变量里面

· 多仓库管理代码的优势

   多仓库在微服务中就是每个服务一个源码仓库 

  1. 每一个服务都有一个独立的仓库,职责单一
  2. 代码量和复杂性受控,服务由不同的团队独立维护、边界清晰
  3. 单个服务也易于自治开发测试部署和扩展,不需要集中管理集中协调
  4. 利于进行权限控制,可以针对单个仓库来分配权限,权限分配力度比较细

· ts 相对于 js 的优势 

  1. ts是js的超集,存在类型的脚本语言
  2. 继承了js的所有编程类型,js代码可在ts环境很好的运行
  3. 为构建大型应用而生,但小程序同样适用
  4. 强大的类型系统,拥有静态类型检查能力
  5. 新增类型注解和类型推断
  6. 拥有丰富的class扩展功能
  7. 添加了系统级设计能力,设计模式由顶层由下进行设计

· JS和TS的区别

· 说出几种vue常见指令

  1. v-for:用 v-for 指令根据遍历数组来进行渲染
  2. v-if:v-if 可以实现条件渲染
  3. v-once:v-once 关联的实例,只会渲染一次
  4. v-bind:用来动态的绑定一个或者多个数据(特性),可以简写为 :
  5. v-model: 主要用于表单的双向数据绑定。
  6. v-show:根据条件展示元素
  7. v-on: 主要用来监听 dom 事件,可以简写为 @

· v-if 和 v-show 有什么区别

  • v-if: v-if 可以实现条件渲染,当条件为真,元素将会被渲染,当条件为假,元素会被销毁。所以v-if多用于切换次数少的地方
  • v-show: v-show只是根据条件展示元素,当条件为真,元素将会被隐藏,当条件为假,元素会被显示。因此v-show多用于经常切换的地方

· HTTP响应码 

  • 1xx 表示【临时响应】并需要请求者继续执行操作的状态代码
  1. 100(继续)请求者应当继续提出请求。
  2. 101(切换协议)请求者已要求服务器切换协议,服务器已确认并准备切换。
  • 2xx 表示【成功】处理了请求的状态代码
  1. 200(成功)服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
  2. 201(已创建)请求成功并且服务器创建了新的资源。
  3. 202(已接受)服务器已接受请求,但尚未处理。
  4. 203(非授权信息)服务器已成功处理了请求,但返回的信息可能来自另一来源。
  5. 204(无内容)服务器成功处理了请求,但没有返回任何内容。
  6. 205(重置内容)服务器成功处理了请求,但没有返回任何内容。
  7. 206(部分内容)服务器成功处理了部分 GET 请求。
  • 3xx 表示要完成请求,需要进一步操作。通常,这些状态代码用来【重定向】
  1. 300(多种选择)针对请求,服务器可执行多种操作。
  2. 301(永久移动)请求的网页已永久移动到新位置。
  3. 302(临时移动)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
  4. 303(查看其他位置)请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
  5. 304(未修改)自从上次请求后,请求的网页未修改过。
  6. 305(使用代理)请求者只能使用代理访问请求的网页。
  7. 307(临时重定向)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
  • 4xx 表示【请求可能出错】,妨碍了服务器的处理
  1. 400(错误请求)表示客户端请求的语法错误,服务器无法理解,例如 url 含有非法字符、json 格式有问题。
  2. 401(未授权)请求要求身份验证。对于需要登录的网页,服务器可能返回此响应。
  3. 402表示保留,将来使用。
  4. 403(禁止)表示服务器理解请求客户端的请求,但是拒绝请求。
  5. 404(未找到)服务器无法根据客户端的请求找到资源(网页)。
  6. 405(方法禁用)禁用请求中指定的方法。
  7. 406(不接受)无法使用请求的内容特性响应请求的网页。
  8. 407(需要代理授权)此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
  9. 408(请求超时)服务器等候请求时发生超时。
  • 5xx 表示【服务器】在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错
  1. 500(服务器内部错误)服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
  2. 501(尚未实施)服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。
  3. 502(错误网关)服务器作为网关或代理,从上游服务器收到无效响应。
  4. 503(服务不可用)服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。
  5. 504(网关超时)服务器作为网关或代理,但是没有及时从上游服务器收到请求。
  6. 505(HTTP 版本不受支持)服务器不支持请求中所用的 HTTP 版本。

  常见的 http 响应码:

  1. 200 - 服务器成功返回网页
  2. 404 - 请求的网页不存在
  3. 503 - 服务不可用

· 如何让CSS只在当前组件中起作用 

   只需要在Style标签添加Scoped属性,即<style scoped></style>

· 单页应用优缺点

   微信小程序也是单页面应用

  • 优点
  1. 良好的交互体验
  2. 良好的前后端工作分离模式
  3. 减轻服务器的压力
  • 缺点
  1. 首屏加载慢
  2. 不利于 SEO

· 浏览器的存储方式

  • 常见的浏览器存储主要有
  1. 属于文档对象模型:documentcookie,
  2. 属于浏览器对象模型:localStorage,sessionStorage,indexDB

· cookie 的有效作用域

    cookie的作用域为当前域及其子域

· webpack 配置

   Webpack是一种前端资源构建工具,一个静态资源打包器。
   在Webpack看来,前端所有的资源文件js、css、json、img、less都会作为模块处理。
   它根据模块的依赖关系进行静态分析,打包生成对应的静态文件。

  • Webpack五大核心概念

      1. Entry
          入口指示 Webpack以哪个文件为入口起点开始打包,分析构建内部依赖图
      2. Output
          输出指示 Webpack打包后的资源 bundles输出到哪里去,以及如何命名
      3. Loder
          让Webpack能够去处理那些非js文件
      4. Plugins
          Plugin是(插件)可以用于执行范围更广的任务,插件的范围包括从打包优化和压缩,一直重新定义环境中的的变量等
      5. Mode
         模式指示 Webpack使用相应的模式配置(development、production)

· react 声明周期

  • 挂载阶段
  1. constructor:构造函数,初始化 state 对象或给自定义方法绑定 this 
  2. getDerivedStateFromProps:接收到新的属性想去修改 state
  3. render:纯函数,只返回需要渲染的东西
  4. componentDidMount: 组件装载之后调用,此时可以获取到DOM节点并操作
  • 更新阶段
  1. getDerivedStateFromProps: 此方法在更新个挂载阶段都可能会调用
  2. shouldComponentUpdate: 有两个参数nextProps和nextState,表示新的属性和变化之后的state返回一个布尔值,true:会触发重新渲染,false:不会触发重新渲染,默认返回true
  3. render: 更新阶段也会触发此生命周期
  4. getSnapshotBeforeUpdate:在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate此生命周期必须与componentDidUpdate搭配使用
  5. componentDidUpdate:有三个参数prevProps,prevState,snapshot,都表示之前的
  • 卸载阶段

        componentWillUnmount: 当组件被卸载或者销毁了就会调用

· Vue 和 React 的区别(使用和原理上都说一下)

  • 响应式原理不同

    Vue

  1. Vue依赖收集,自动优化数据可变。
  2. Vue递归监听data的所有属性,直接修改。
  3. 当数据改变时,自动找到引用组件重新渲染。

   React

  1. React基于状态机,手动优化,数据不可变,需要setState驱动新的State替换老的State。

  2. 当数据改变时,以组件为根目录,默认全部重新渲染
  •  diff算法不同

     两者流程思维上是类似的,都是基于两个假设(使得算法复杂度降为O(n)):

  1. 不同的组件产生不同的 DOM 结构。当type不相同时,对应DOM操作就是直接销毁老的DOM,创建新的DOM。
  2. 同一层次的一组子节点,可以通过唯一的 key 区分。

     但两者源码实现上有区别:
     Vue基于snabbdom库,它有较好的速度以及模块机制。Vue Diff使用双向链表,边对比,边更新DOM。
  React主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。 

  •   事件机制不同

     Vue

  1. Vue原生事件使用标准Web事件
  2. Vue组件自定义事件机制,是父子组件通信基础
  3. Vue合理利用了snabbdom库的模块插件

     React

  1. React原生事件被包装,所有事件都冒泡到顶层document监听,然后在这里合成事件下发。基于这套,可以跨端使用事件机制,而不是和Web DOM强绑定。
  2. React组件上无事件,父子组件通信使用props

· c/c++和 js 区别 

  1. C++是静态语言,js是动态语言
  2. C++是编译型语言,js是解释型语言
  3. C++有指针,js无指针
  4. js是函数式编程语言,C++不是
  5. C++的继承是基于类的,js的继承基于原型

· 进程间通信

  • 本地进程之间的通信方式(IPC)
  1. 消息传递
  2. 信号量
  3. 共享内存
  4. 远程过程调用
  • 远程进程之间的通信方式

       套接字(ip地址:端口号):标识网络的进程

· HTTP1.0和HTTP2.0的区别、HTTP和HTTPS的区别

  • HTTP1.0和HTTP2.0的区别
  1. HTTP/2采用二进制格式而非文本格式
  2. HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行
  3. 使用报头压缩,HTTP/2降低了开销
  4. HTTP/2让服务器可以将响应主动“推送”到客户端缓存中
  • HTTP和HTTPS的区别 
  1. https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
  2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  3. http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  4. http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

· 为什么说组合优于继承 

可以极大程度地减少子系统具体实现之间的相互依赖

· CSS 格式化上下文

  • BFC:块级格式化上下文

一个元素具有 BFC,内部子元素再怎么变化,都不会影响外部的元素。这是BFC最核心的特点

     应用场景:

  1. 防止垂直方向margin重叠
  2. 创建自适应两栏布局
  3. 清除浮动(解决浮动元素父元素高度塌陷)
  • IFC:行内格式上下文
  • GFC:网格布局格式化上下文(Grids网格布局)
  • FFC:自适应格式上下文(flex布局)

· 清除浮动的四种方法

  • 给浮动的元素的祖先元素加上高度
  • clear:both;
  • 隔墙法与内墙法

       隔墙法 该方法通过div.cl h16这堵墙将两个父类分隔,弥补了clear:both方法中margin无效的情况,可以通过设置墙的高度来控制间隙

<div></div>
<div class="cl h16"></div>
<div></div>
.cl{
  clear: both;
}
.hl6{
  height: 16px;
}

       内墙法 顾名思义,将墙修在了父类里面

<div>
  <p></p>
  <div class="cl h16"></div>
</div>
  • overflow:hidden;

      父类加overflow:hidden;,将超出父类的部分隐藏

· Vue双向绑定

  1. 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
  2. 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
  3. 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器

· 实现三列布局的四种方法

  1. 浮动布局(float)
  2. 定位布局(position)
  3. 圣杯布局
  4. 双飞翼布局

· React 虚拟DOM

   虚拟DOM就是一个JS对象,通过对象的方式来表示DOM结构,React在内存中维护一个跟真实DOM一样的虚拟DOM树,再改动完组件后,会再生成一个新的虚拟DOM,React会将新的虚拟DOM和原来的虚拟DOM进行对比,找出两个DOM树的不同的地方(diff),然后在真实DOM上更新diff,提高渲染速度

  • diff算法的原理

      虚拟DOM树发生变化后,生成DOM树更新补丁的方式、它通过对比新旧两颗虚拟DOM树的变更差异,将更新补丁作用于真实DOM,以最小的成本完成试图更新

  • 具体流程
  1. 真实DOM首先映射为虚拟DOM
  2. 当虚拟DOM发生变化时,根据变化计算生成patch,这个patch就是一个结构化的数据,包含了增加、更新、删除等操作。
  3. 根据patch去更新真是DOM,反馈到用户页面上

· React中的key是什么

   key是React用于追踪哪些列表中元素被修改、被添加、被移除的辅助标识。在开发中,我们要保证每个元素的key在其同级的元素具有唯一性。

   diff算法中会借助key来判断该元素是新创建的还是被移动而来的元素,从而减少不必要的渲染。

  1. key具有唯一性
  2. 尽量不要用数组中的index作为key
  3. 不要再render的时候使用随机数或其它操作给元素加上不稳定的key,因为这样造成的性能开销比不加key更多

· 虚拟DOM和真实DOM的区别

  1. 虚拟dom(VNode),假的,不是真实的dom
  2. 虚拟DOM不会进行排版与重绘操作    虚拟DOM就是把真实DOM转换为Javascript代码
  3. 真实的DOM在浏览器通过dom.api操作的,复杂的对象
  4. 虚拟DOM:可以通过this.$slots.default查看
  5. 真实的dom是一个对象,它的属性非常多,在浏览器中做dom操作,会比较消耗性能
  6. 虚拟dom是一个对象,它的属性相比较于真实的dom就比较少---用少量的属性描述一个dom,无法在浏览器中直接显示

· React组件之间的8种通讯方式

· 手写JS观察者模式

   当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式

  • 观察者(订阅者) –Watcher

   updata()当事件发生时,具体要做的事情

  • 发布者 –Dep
  1. subs数组存储所有的观察者
  2. addSub()添加观察者
  3. notify当事件发生,循环所有subs中的观察者的updata方法
//发布者
class Dep {
  constructor () {
    this.subs = []
  }
  addSub (sub) {
    if(sub && sub.updata){
      this.subs.push(sub)
    }
  }
  notify () {
    this.subs.forEach((element)=>{
      element.updata()
    })
  }
}
//观察者
class watch {
  updata() {
    console.log('观察者')
  }
}
const dep = new Dep()
dep.addSub (new watch())
dep.notify()

· 代码题(查找数组中某个数的下标)(二分查找/折半查找)

  • 前提条件

       二分查找要求数组数据必须采用顺序存储结构有序排列。

  • 原理

       首先,假设数组中元素是按升序排列,将数组中间位置的数据与查找数据比较,如果两者相等,则查找成功;否则利用中间位置记录将数组分成前、后两个子数组,如果中间位置数据大于查找数据,则进一步查找前子数组,否则进一步查找后子数组。重复以上过程,直到找到满足条件的数据,则表示查找成功,直到子数组不存在为止,表示查找不成功。

import Java.util.Scanner;

public static void main(String[] args) {	
    Scanner scan = new Scanner(System.in);
	int[] nums = new int[9];
	//初始化数组,随机100以内的数
	for (int i = 0; i < nums.length; i++) {
		nums[i]=(int) (Math.random()*100);
	}
	System.out.println("初始数组为:");
	for (int i : nums) {
		System.out.print(i+"\t");
	}
	//二分查找需要一个有顺序的数组,所以对初始化的数组进行排序
	for (int i = 0; i < nums.length-1; i++) {
		for (int j = 0; j < nums.length-i-1; j++) {
			if (nums[j]>nums[j+1]) {
				int temp=nums[j];
				nums[j]=nums[j+1];
				nums[j+1]=temp;
			}
		}
	}
	System.out.println("\n排序后数组为:");
	for (int i = 0; i < nums.length; i++) {
		System.out.print(nums[i]+"\t");
	}
	//二分查找
	//最小下标
	int minIndex=0;
	//最大下标
	int maxIndex=nums.length-1;
	//中间下标
	int centerIndex=(minIndex+maxIndex)/2;
	//查找元素
	System.out.print("\n请输入需要查找的元素:");
	int findNum=scan.nextInt();
	while (true) {
		if (nums[centerIndex]>findNum) {
			//当查找目标小于中间值时,此时目标值在左边,此时最大下标为中间下标-1
			maxIndex=centerIndex-1;
		} else if (nums[centerIndex]<findNum) {
			//当查找目标大于中间值时,此时目标值在右边,此时最小下标为中间下标+1
			minIndex=centerIndex+1;
		}else {
			System.out.println("查找元素"+findNum+"的下标为:"+centerIndex);
			break;
		}
		if (minIndex>maxIndex) {
			//当最小下标大于最大下标时,说明找不到目标值
			System.out.println("查找元素的下标不存在");
			break;
		}
		//每次查找后边界都会发生变化,所以就重新更新中间下标
		centerIndex=(minIndex+maxIndex)/2;
	}
	// 关闭扫描,释放资源
	scan.close();
}

· 代码题(三数之和)(双指针法)

   

public List<List<Integer>> threeSum(int[] nums) {
   List<List<Integer>> list = new LinkedList<>();
   Arrays.sort(nums);
   for (int i = 0; i < nums.length - 2; i++) {
       if(nums[i] > 0) {
           break;
       }
       if(i > 0 && nums[i] == nums[i - 1]) {
           continue;
       }
       int left = i + 1;
       int right = nums.length - 1;
       while(left < right) {
           if(left > i + 1 && nums[left] == nums[left - 1]) {
               left++;
               continue;
            }
            if(right < nums.length - 1 && nums[right] == nums[right + 1]) {
               right--;
               continue;
            }
            int target = nums[i] + nums[left++] + nums[right--];
            if(target > 0) {
                right--;
            } else if(target < 0) {
                left++;
            } else {
                List<Integer> temp = new LinkedList<>();
                temp.add(nums[i]); temp.add(nums[left]); temp.add(nums[right]);
                list.add(temp);
            }
        }
    }
    return list;
  }

· 代码题(任务队列)

设计一个简单的任务队列, 要求分别在 1,3,4 秒后打印出 "1", "2", "3"

async function one() {
    setTimeout(function() {
        console.log(1);
    }, 1000);}
async function two() {
    setTimeout(function() {
        console.log(2);
    }, 3000);}
async function three() {
    setTimeout(function() {
        console.log(3);
    }, 4000);}
async function run() {
    var p1 = await one();
    var p2 = await two();
    var p3 = await three();
}
run(); 

· 代码题(找出n个元素数组中重复次数最多的数)  

public class Solution {
    public int majorityElement(int[] nums) {
        int count = 0;
        int temp = 0;
        for(int i= 0;i<nums.length;i++){
            if(count == 0){
                temp = nums[i];
                count++;
            }else{
                if(temp == nums[i])
                    count++;
                else
                    count--;
            }
        }
        return temp;
    }
}

· 代码题(字符数组全排列) 

public static void main(String[] args) {
   char[] chars = {'1','2','3','4'};
   List<String> result = new ArrayList<>();
   result = stringFullPermutation(chars,0,result);
   for(String s : result)
       System.out.println(s);
}
private static  List<String> stringFullPermutation(char[] chars,int i,List<String> res){
   if(i == chars.length)
       return res;
   List<String> result = new ArrayList<>();
   if(i == 0){
       result.add(String.valueOf(chars[i]));
   }
   else {
       for(String s : res){
           for(int j = 0;j <= s.length();j ++){
               result.add(s.substring(0,j) + chars[i] + s.substring(j,s.length()));
           }
       }
   }
   return stringFullPermutation(chars,i + 1,result);
}

· 代码题(顺时针打印二维数组)

package 多维数组与矩阵;
/**
 * 输入:
 * 1 2 3 4
 * 5 6 7 8
 * 9 10 11 12
 * 13 14 15 16
 * 输出:1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10
 */
public class 顺时针打印二维数组 {
    public static void main(String[] args) {
        int[][] arr={
                {1,2,3,4},
                {5,6,7,8},
                {9,10,11,12},
                {13,14,15,16},
        };
        f(arr);
    }
    static void f(int[][] arr) {
        //定义左上角和右下角的下标
        int leftUpRow = 0, leftUpCol = 0, rightDownRow = arr.length - 1, rightDownCol = arr[0].length - 1;
        while (leftUpRow<=rightDownRow&&leftUpCol<=rightDownCol) {
            int r = leftUpRow, c = leftUpCol;
            //上面一条边
            while (c <= rightDownCol) {
                System.out.print(arr[r][c++] + " ");
            }
            //恢复
            c = rightDownCol;
            r++;
            //右面一条边
            while (r <= rightDownRow) {
                System.out.print(arr[r++][c] + " ");
            }
            //恢复
            r = rightDownRow;
            c--;
            //最下面一条边
            while (c >= leftUpCol) {
                System.out.print(arr[r][c--] + " ");
            }
            //恢复
            c = leftUpCol;
            r--;
            //最左边一条边
            while (r > leftUpRow) {
                System.out.print(arr[r--][c] + " ");
            }
            leftUpRow++;
            leftUpCol++;
            rightDownRow--;
            rightDownCol--;
        }
    }
}

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫没有猫耳朵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值